feat: add token func
This commit is contained in:
parent
0e7f0a2b68
commit
e8c5616206
20
Makefile
20
Makefile
|
@ -6,7 +6,7 @@ GOFMT ?= gofmt "-s"
|
|||
GOFILES := $(shell find . -name "*.go")
|
||||
LDFLAGS := -s -w
|
||||
VERSION="v1.0.1"
|
||||
DOCKER_REPO="igs170911/permission"
|
||||
DOCKER_REPO="code.30cm.net/permission"
|
||||
|
||||
.PHONY: test
|
||||
test: # 進行測試
|
||||
|
@ -46,24 +46,10 @@ build-docker:
|
|||
rm -rf Dockerfile
|
||||
@echo "Generate core-api files successfully"
|
||||
|
||||
.PHONY: gen-my-sql-model-up
|
||||
gen-my-sql-model: # 建立 rpc 資料庫
|
||||
goctl model mysql ddl -c no -s ./generate/database/mysql/20240816014305_create_permission_table.up.sql --style $(GO_ZERO_STYLE) -d ./internal/model -i ''
|
||||
goctl model mysql ddl -c no -s ./generate/database/mysql/20240819013052_create_roles_table.up.sql --style $(GO_ZERO_STYLE) -d ./internal/model -i ''
|
||||
goctl model mysql ddl -c no -s ./generate/database/mysql/20240819022436_create_user_role_table.up.sql --style $(GO_ZERO_STYLE) -d ./internal/model -i ''
|
||||
goctl model mysql ddl -c no -s ./generate/database/mysql/20240819090248_create_role_permission_table.up.sql --style $(GO_ZERO_STYLE) -d ./internal/model -i ''
|
||||
@echo "Generate mysql model files successfully"
|
||||
|
||||
.PHONY: mock-gen
|
||||
mock-gen: # 建立 mock 資料
|
||||
mockgen -source=./internal/model/permission_model.go -destination=./internal/mock/model/permission_model.go -package=mock
|
||||
mockgen -source=./internal/model/permission_model_gen.go -destination=./internal/mock/model/permission_model_gen.go -package=mock
|
||||
mockgen -source=./internal/model/role_model.go -destination=./internal/mock/model/role_model.go -package=mock
|
||||
mockgen -source=./internal/model/role_model_gen.go -destination=./internal/mock/model/role_model_gen.go -package=mock
|
||||
mockgen -source=./internal/model/role_permission_model.go -destination=./internal/mock/model/role_permission_model.go -package=mock
|
||||
mockgen -source=./internal/model/role_permission_model_gen.go -destination=./internal/mock/model/role_permission_model_gen.go -package=mock
|
||||
mockgen -source=./internal/model/user_role_model.go -destination=./internal/mock/model/user_role_model.go -package=mock
|
||||
mockgen -source=./internal/model/user_role_model_gen.go -destination=./internal/mock/model/user_role_model_gen.go -package=mock
|
||||
mockgen -source=./pkg/domain/repository/token.go -destination=./pkg/mock/repository/token.go -package=mock
|
||||
@echo "Generate mock files successfully"
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
# BUILDER #
|
||||
###########
|
||||
|
||||
FROM golang:1.23.4 AS builder
|
||||
FROM golang:1.24.0 AS builder
|
||||
|
||||
ARG VERSION
|
||||
ARG BUILT
|
||||
|
|
|
@ -2,7 +2,7 @@ syntax = "proto3";
|
|||
|
||||
package permission;
|
||||
|
||||
option go_package="./app-cloudep-permission-server";
|
||||
option go_package="./permission";
|
||||
|
||||
// OKResp
|
||||
message OKResp {}
|
||||
|
|
7
go.mod
7
go.mod
|
@ -1,11 +1,16 @@
|
|||
module code.30cm.net/digimon/app-cloudep-permission-server
|
||||
|
||||
go 1.23.4
|
||||
go 1.23.6
|
||||
|
||||
require (
|
||||
code.30cm.net/digimon/library-go/errs v1.2.14
|
||||
github.com/alicebob/miniredis/v2 v2.34.0
|
||||
github.com/golang-jwt/jwt/v4 v4.5.1
|
||||
github.com/pkg/errors v0.9.1
|
||||
github.com/segmentio/ksuid v1.0.4
|
||||
github.com/stretchr/testify v1.10.0
|
||||
github.com/zeromicro/go-zero v1.8.0
|
||||
go.uber.org/mock v0.5.0
|
||||
google.golang.org/grpc v1.70.0
|
||||
google.golang.org/protobuf v1.36.5
|
||||
)
|
||||
|
|
12
go.sum
12
go.sum
|
@ -1,3 +1,5 @@
|
|||
code.30cm.net/digimon/library-go/errs v1.2.14 h1:Un9wcIIjjJW8D2i0ISf8ibzp9oNT4OqLsaSKW0T4RJU=
|
||||
code.30cm.net/digimon/library-go/errs v1.2.14/go.mod h1:Hs4v7SbXNggDVBGXSYsFMjkii1qLF+rugrIpWePN4/o=
|
||||
github.com/alicebob/gopher-json v0.0.0-20230218143504-906a9b012302 h1:uvdUDbHQHO85qeSydJtItA4T55Pw6BtAejd0APRJOCE=
|
||||
github.com/alicebob/gopher-json v0.0.0-20230218143504-906a9b012302/go.mod h1:SGnFV6hVsYE877CKEZ6tDNTjaSXYUk6QqoIK6PrAtcc=
|
||||
github.com/alicebob/miniredis/v2 v2.34.0 h1:mBFWMaJSNL9RwdGRyEDoAAv8OQc5UlEhLDQggTglU/0=
|
||||
|
@ -46,6 +48,8 @@ github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4
|
|||
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
|
||||
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
|
||||
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
|
||||
github.com/golang-jwt/jwt/v4 v4.5.1 h1:JdqV9zKUdtaa9gdPlywC3aeoEsR681PlKC+4F5gQgeo=
|
||||
github.com/golang-jwt/jwt/v4 v4.5.1/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=
|
||||
github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc=
|
||||
github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs=
|
||||
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
|
||||
|
@ -123,6 +127,8 @@ github.com/redis/go-redis/v9 v9.7.0 h1:HhLSs+B6O021gwzl+locl0zEDnyNkxMtf/Z3NNBMa
|
|||
github.com/redis/go-redis/v9 v9.7.0/go.mod h1:f6zhXITC7JUJIlPEiBOTXxJgPLdZcA93GewI7inzyWw=
|
||||
github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ=
|
||||
github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog=
|
||||
github.com/segmentio/ksuid v1.0.4 h1:sBo2BdShXjmcugAMwjugoGUdUV0pcxY5mW4xKRn3v4c=
|
||||
github.com/segmentio/ksuid v1.0.4/go.mod h1:/XUiZBD3kVx5SmUOl55voK5yeAbBNNIed+2O73XgrPE=
|
||||
github.com/spaolacci/murmur3 v1.1.0 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0bLI=
|
||||
github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
|
||||
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
|
||||
|
@ -183,6 +189,8 @@ go.uber.org/automaxprocs v1.6.0 h1:O3y2/QNTOdbF+e/dpXNNW7Rx2hZ4sTIPyybbxyNqTUs=
|
|||
go.uber.org/automaxprocs v1.6.0/go.mod h1:ifeIMSnPZuznNm6jmdzmU3/bfk01Fe2fotchwEFJ8r8=
|
||||
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
|
||||
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
|
||||
go.uber.org/mock v0.5.0 h1:KAMbZvZPyBPWgD14IrIQ38QCyjwpvVVV6K/bHl1IwQU=
|
||||
go.uber.org/mock v0.5.0/go.mod h1:ge71pBPLYDk7QIi1LupWxdAykm7KIEFchiOqd6z7qMM=
|
||||
go.uber.org/multierr v1.9.0 h1:7fIwc/ZtS0q++VgcfqFDxSBZVv/Xo49/SYnDFupUwlI=
|
||||
go.uber.org/multierr v1.9.0/go.mod h1:X2jQV1h+kxSjClGpnseKVIxpmcjrj7MNnI0bnlfKTVQ=
|
||||
go.uber.org/zap v1.24.0 h1:FiJd5l1UOLj0wCgbSE0rwwXHzEdAZS6hiiSnxJN/D60=
|
||||
|
@ -230,8 +238,8 @@ golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtn
|
|||
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
||||
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d h1:vU5i/LfpvrRCpgM/VPfJLg5KjxD3E+hfT1SH+d9zLwg=
|
||||
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=
|
||||
golang.org/x/tools v0.22.0 h1:gqSGLZqv+AI9lIQzniJ0nZDRG5GBPsSi+DRNHWNz6yA=
|
||||
golang.org/x/tools v0.22.0/go.mod h1:aCwcsjqvq7Yqt6TNyX7QMU2enbQ/Gt0bo6krSeEri+c=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
|
|
|
@ -3,7 +3,7 @@ package tokenservicelogic
|
|||
import (
|
||||
"context"
|
||||
|
||||
"code.30cm.net/digimon/app-cloudep-permission-server/gen_result/pb/app-cloudep-permission-server"
|
||||
"code.30cm.net/digimon/app-cloudep-permission-server/gen_result/pb/permission"
|
||||
"code.30cm.net/digimon/app-cloudep-permission-server/internal/svc"
|
||||
|
||||
"github.com/zeromicro/go-zero/core/logx"
|
||||
|
@ -24,8 +24,8 @@ func NewCancelOneTimeTokenLogic(ctx context.Context, svcCtx *svc.ServiceContext)
|
|||
}
|
||||
|
||||
// CancelOneTimeToken 取消一次性使用
|
||||
func (l *CancelOneTimeTokenLogic) CancelOneTimeToken(in *app_cloudep_permission_server.CancelOneTimeTokenReq) (*app_cloudep_permission_server.OKResp, error) {
|
||||
func (l *CancelOneTimeTokenLogic) CancelOneTimeToken(in *permission.CancelOneTimeTokenReq) (*permission.OKResp, error) {
|
||||
// todo: add your logic here and delete this line
|
||||
|
||||
return &app_cloudep_permission_server.OKResp{}, nil
|
||||
return &permission.OKResp{}, nil
|
||||
}
|
||||
|
|
|
@ -3,7 +3,7 @@ package tokenservicelogic
|
|||
import (
|
||||
"context"
|
||||
|
||||
"code.30cm.net/digimon/app-cloudep-permission-server/gen_result/pb/app-cloudep-permission-server"
|
||||
"code.30cm.net/digimon/app-cloudep-permission-server/gen_result/pb/permission"
|
||||
"code.30cm.net/digimon/app-cloudep-permission-server/internal/svc"
|
||||
|
||||
"github.com/zeromicro/go-zero/core/logx"
|
||||
|
@ -24,8 +24,8 @@ func NewCancelTokenByDeviceIdLogic(ctx context.Context, svcCtx *svc.ServiceConte
|
|||
}
|
||||
|
||||
// CancelTokenByDeviceId 取消 Token, 從 Device 視角出發,可以選,登出這個Device 下所有 token ,登出這個Device 下指定token
|
||||
func (l *CancelTokenByDeviceIdLogic) CancelTokenByDeviceId(in *app_cloudep_permission_server.DoTokenByDeviceIDReq) (*app_cloudep_permission_server.OKResp, error) {
|
||||
func (l *CancelTokenByDeviceIdLogic) CancelTokenByDeviceId(in *permission.DoTokenByDeviceIDReq) (*permission.OKResp, error) {
|
||||
// todo: add your logic here and delete this line
|
||||
|
||||
return &app_cloudep_permission_server.OKResp{}, nil
|
||||
return &permission.OKResp{}, nil
|
||||
}
|
||||
|
|
|
@ -3,7 +3,7 @@ package tokenservicelogic
|
|||
import (
|
||||
"context"
|
||||
|
||||
"code.30cm.net/digimon/app-cloudep-permission-server/gen_result/pb/app-cloudep-permission-server"
|
||||
"code.30cm.net/digimon/app-cloudep-permission-server/gen_result/pb/permission"
|
||||
"code.30cm.net/digimon/app-cloudep-permission-server/internal/svc"
|
||||
|
||||
"github.com/zeromicro/go-zero/core/logx"
|
||||
|
@ -24,8 +24,8 @@ func NewCancelTokenLogic(ctx context.Context, svcCtx *svc.ServiceContext) *Cance
|
|||
}
|
||||
|
||||
// CancelToken 取消 Token,也包含他裡面的 One Time Toke
|
||||
func (l *CancelTokenLogic) CancelToken(in *app_cloudep_permission_server.CancelTokenReq) (*app_cloudep_permission_server.OKResp, error) {
|
||||
func (l *CancelTokenLogic) CancelToken(in *permission.CancelTokenReq) (*permission.OKResp, error) {
|
||||
// todo: add your logic here and delete this line
|
||||
|
||||
return &app_cloudep_permission_server.OKResp{}, nil
|
||||
return &permission.OKResp{}, nil
|
||||
}
|
||||
|
|
|
@ -3,7 +3,7 @@ package tokenservicelogic
|
|||
import (
|
||||
"context"
|
||||
|
||||
"code.30cm.net/digimon/app-cloudep-permission-server/gen_result/pb/app-cloudep-permission-server"
|
||||
"code.30cm.net/digimon/app-cloudep-permission-server/gen_result/pb/permission"
|
||||
"code.30cm.net/digimon/app-cloudep-permission-server/internal/svc"
|
||||
|
||||
"github.com/zeromicro/go-zero/core/logx"
|
||||
|
@ -24,8 +24,8 @@ func NewCancelTokensLogic(ctx context.Context, svcCtx *svc.ServiceContext) *Canc
|
|||
}
|
||||
|
||||
// CancelTokens 取消 Token 從UID 視角,以及 token id 視角出發, UID 登出,底下所有 Device ID 也要登出, Token ID 登出, 所有 UID + Device 都要登出
|
||||
func (l *CancelTokensLogic) CancelTokens(in *app_cloudep_permission_server.DoTokenByUIDReq) (*app_cloudep_permission_server.OKResp, error) {
|
||||
func (l *CancelTokensLogic) CancelTokens(in *permission.DoTokenByUIDReq) (*permission.OKResp, error) {
|
||||
// todo: add your logic here and delete this line
|
||||
|
||||
return &app_cloudep_permission_server.OKResp{}, nil
|
||||
return &permission.OKResp{}, nil
|
||||
}
|
||||
|
|
|
@ -3,7 +3,7 @@ package tokenservicelogic
|
|||
import (
|
||||
"context"
|
||||
|
||||
"code.30cm.net/digimon/app-cloudep-permission-server/gen_result/pb/app-cloudep-permission-server"
|
||||
"code.30cm.net/digimon/app-cloudep-permission-server/gen_result/pb/permission"
|
||||
"code.30cm.net/digimon/app-cloudep-permission-server/internal/svc"
|
||||
|
||||
"github.com/zeromicro/go-zero/core/logx"
|
||||
|
@ -24,8 +24,8 @@ func NewGetUserTokensByDeviceIdLogic(ctx context.Context, svcCtx *svc.ServiceCon
|
|||
}
|
||||
|
||||
// GetUserTokensByDeviceId 取得目前所對應的 DeviceID 所存在的 Tokens
|
||||
func (l *GetUserTokensByDeviceIdLogic) GetUserTokensByDeviceId(in *app_cloudep_permission_server.DoTokenByDeviceIDReq) (*app_cloudep_permission_server.Tokens, error) {
|
||||
func (l *GetUserTokensByDeviceIdLogic) GetUserTokensByDeviceId(in *permission.DoTokenByDeviceIDReq) (*permission.Tokens, error) {
|
||||
// todo: add your logic here and delete this line
|
||||
|
||||
return &app_cloudep_permission_server.Tokens{}, nil
|
||||
return &permission.Tokens{}, nil
|
||||
}
|
||||
|
|
|
@ -3,7 +3,7 @@ package tokenservicelogic
|
|||
import (
|
||||
"context"
|
||||
|
||||
"code.30cm.net/digimon/app-cloudep-permission-server/gen_result/pb/app-cloudep-permission-server"
|
||||
"code.30cm.net/digimon/app-cloudep-permission-server/gen_result/pb/permission"
|
||||
"code.30cm.net/digimon/app-cloudep-permission-server/internal/svc"
|
||||
|
||||
"github.com/zeromicro/go-zero/core/logx"
|
||||
|
@ -24,8 +24,8 @@ func NewGetUserTokensByUidLogic(ctx context.Context, svcCtx *svc.ServiceContext)
|
|||
}
|
||||
|
||||
// GetUserTokensByUid 取得目前所對應的 UID 所存在的 Tokens
|
||||
func (l *GetUserTokensByUidLogic) GetUserTokensByUid(in *app_cloudep_permission_server.QueryTokenByUIDReq) (*app_cloudep_permission_server.Tokens, error) {
|
||||
func (l *GetUserTokensByUidLogic) GetUserTokensByUid(in *permission.QueryTokenByUIDReq) (*permission.Tokens, error) {
|
||||
// todo: add your logic here and delete this line
|
||||
|
||||
return &app_cloudep_permission_server.Tokens{}, nil
|
||||
return &permission.Tokens{}, nil
|
||||
}
|
||||
|
|
|
@ -3,7 +3,7 @@ package tokenservicelogic
|
|||
import (
|
||||
"context"
|
||||
|
||||
"code.30cm.net/digimon/app-cloudep-permission-server/gen_result/pb/app-cloudep-permission-server"
|
||||
"code.30cm.net/digimon/app-cloudep-permission-server/gen_result/pb/permission"
|
||||
"code.30cm.net/digimon/app-cloudep-permission-server/internal/svc"
|
||||
|
||||
"github.com/zeromicro/go-zero/core/logx"
|
||||
|
@ -24,8 +24,8 @@ func NewNewOneTimeTokenLogic(ctx context.Context, svcCtx *svc.ServiceContext) *N
|
|||
}
|
||||
|
||||
// NewOneTimeToken 建立一次性使用,例如:RefreshToken
|
||||
func (l *NewOneTimeTokenLogic) NewOneTimeToken(in *app_cloudep_permission_server.CreateOneTimeTokenReq) (*app_cloudep_permission_server.CreateOneTimeTokenResp, error) {
|
||||
func (l *NewOneTimeTokenLogic) NewOneTimeToken(in *permission.CreateOneTimeTokenReq) (*permission.CreateOneTimeTokenResp, error) {
|
||||
// todo: add your logic here and delete this line
|
||||
|
||||
return &app_cloudep_permission_server.CreateOneTimeTokenResp{}, nil
|
||||
return &permission.CreateOneTimeTokenResp{}, nil
|
||||
}
|
||||
|
|
|
@ -3,7 +3,7 @@ package tokenservicelogic
|
|||
import (
|
||||
"context"
|
||||
|
||||
"code.30cm.net/digimon/app-cloudep-permission-server/gen_result/pb/app-cloudep-permission-server"
|
||||
"code.30cm.net/digimon/app-cloudep-permission-server/gen_result/pb/permission"
|
||||
"code.30cm.net/digimon/app-cloudep-permission-server/internal/svc"
|
||||
|
||||
"github.com/zeromicro/go-zero/core/logx"
|
||||
|
@ -24,8 +24,8 @@ func NewNewTokenLogic(ctx context.Context, svcCtx *svc.ServiceContext) *NewToken
|
|||
}
|
||||
|
||||
// NewToken 建立一個新的 Token,例如:AccessToken
|
||||
func (l *NewTokenLogic) NewToken(in *app_cloudep_permission_server.AuthorizationReq) (*app_cloudep_permission_server.TokenResp, error) {
|
||||
func (l *NewTokenLogic) NewToken(in *permission.AuthorizationReq) (*permission.TokenResp, error) {
|
||||
// todo: add your logic here and delete this line
|
||||
|
||||
return &app_cloudep_permission_server.TokenResp{}, nil
|
||||
return &permission.TokenResp{}, nil
|
||||
}
|
||||
|
|
|
@ -3,7 +3,7 @@ package tokenservicelogic
|
|||
import (
|
||||
"context"
|
||||
|
||||
"code.30cm.net/digimon/app-cloudep-permission-server/gen_result/pb/app-cloudep-permission-server"
|
||||
"code.30cm.net/digimon/app-cloudep-permission-server/gen_result/pb/permission"
|
||||
"code.30cm.net/digimon/app-cloudep-permission-server/internal/svc"
|
||||
|
||||
"github.com/zeromicro/go-zero/core/logx"
|
||||
|
@ -24,8 +24,8 @@ func NewRefreshTokenLogic(ctx context.Context, svcCtx *svc.ServiceContext) *Refr
|
|||
}
|
||||
|
||||
// RefreshToken 更新目前的token 以及裡面包含的一次性 Token
|
||||
func (l *RefreshTokenLogic) RefreshToken(in *app_cloudep_permission_server.RefreshTokenReq) (*app_cloudep_permission_server.RefreshTokenResp, error) {
|
||||
func (l *RefreshTokenLogic) RefreshToken(in *permission.RefreshTokenReq) (*permission.RefreshTokenResp, error) {
|
||||
// todo: add your logic here and delete this line
|
||||
|
||||
return &app_cloudep_permission_server.RefreshTokenResp{}, nil
|
||||
return &permission.RefreshTokenResp{}, nil
|
||||
}
|
||||
|
|
|
@ -3,7 +3,7 @@ package tokenservicelogic
|
|||
import (
|
||||
"context"
|
||||
|
||||
"code.30cm.net/digimon/app-cloudep-permission-server/gen_result/pb/app-cloudep-permission-server"
|
||||
"code.30cm.net/digimon/app-cloudep-permission-server/gen_result/pb/permission"
|
||||
"code.30cm.net/digimon/app-cloudep-permission-server/internal/svc"
|
||||
|
||||
"github.com/zeromicro/go-zero/core/logx"
|
||||
|
@ -24,8 +24,8 @@ func NewValidationTokenLogic(ctx context.Context, svcCtx *svc.ServiceContext) *V
|
|||
}
|
||||
|
||||
// ValidationToken 驗證這個 Token 有沒有效
|
||||
func (l *ValidationTokenLogic) ValidationToken(in *app_cloudep_permission_server.ValidationTokenReq) (*app_cloudep_permission_server.ValidationTokenResp, error) {
|
||||
func (l *ValidationTokenLogic) ValidationToken(in *permission.ValidationTokenReq) (*permission.ValidationTokenResp, error) {
|
||||
// todo: add your logic here and delete this line
|
||||
|
||||
return &app_cloudep_permission_server.ValidationTokenResp{}, nil
|
||||
return &permission.ValidationTokenResp{}, nil
|
||||
}
|
||||
|
|
|
@ -7,14 +7,15 @@ package server
|
|||
import (
|
||||
"context"
|
||||
|
||||
"code.30cm.net/digimon/app-cloudep-permission-server/gen_result/pb/app-cloudep-permission-server"
|
||||
"code.30cm.net/digimon/app-cloudep-permission-server/internal/logic/tokenservice"
|
||||
tokenservicelogic "code.30cm.net/digimon/app-cloudep-permission-server/internal/logic/tokenservice"
|
||||
|
||||
"code.30cm.net/digimon/app-cloudep-permission-server/gen_result/pb/permission"
|
||||
"code.30cm.net/digimon/app-cloudep-permission-server/internal/svc"
|
||||
)
|
||||
|
||||
type TokenServiceServer struct {
|
||||
svcCtx *svc.ServiceContext
|
||||
app_cloudep_permission_server.UnimplementedTokenServiceServer
|
||||
permission.UnimplementedTokenServiceServer
|
||||
}
|
||||
|
||||
func NewTokenServiceServer(svcCtx *svc.ServiceContext) *TokenServiceServer {
|
||||
|
@ -24,61 +25,61 @@ func NewTokenServiceServer(svcCtx *svc.ServiceContext) *TokenServiceServer {
|
|||
}
|
||||
|
||||
// NewToken 建立一個新的 Token,例如:AccessToken
|
||||
func (s *TokenServiceServer) NewToken(ctx context.Context, in *app_cloudep_permission_server.AuthorizationReq) (*app_cloudep_permission_server.TokenResp, error) {
|
||||
func (s *TokenServiceServer) NewToken(ctx context.Context, in *permission.AuthorizationReq) (*permission.TokenResp, error) {
|
||||
l := tokenservicelogic.NewNewTokenLogic(ctx, s.svcCtx)
|
||||
return l.NewToken(in)
|
||||
}
|
||||
|
||||
// RefreshToken 更新目前的token 以及裡面包含的一次性 Token
|
||||
func (s *TokenServiceServer) RefreshToken(ctx context.Context, in *app_cloudep_permission_server.RefreshTokenReq) (*app_cloudep_permission_server.RefreshTokenResp, error) {
|
||||
func (s *TokenServiceServer) RefreshToken(ctx context.Context, in *permission.RefreshTokenReq) (*permission.RefreshTokenResp, error) {
|
||||
l := tokenservicelogic.NewRefreshTokenLogic(ctx, s.svcCtx)
|
||||
return l.RefreshToken(in)
|
||||
}
|
||||
|
||||
// CancelToken 取消 Token,也包含他裡面的 One Time Toke
|
||||
func (s *TokenServiceServer) CancelToken(ctx context.Context, in *app_cloudep_permission_server.CancelTokenReq) (*app_cloudep_permission_server.OKResp, error) {
|
||||
func (s *TokenServiceServer) CancelToken(ctx context.Context, in *permission.CancelTokenReq) (*permission.OKResp, error) {
|
||||
l := tokenservicelogic.NewCancelTokenLogic(ctx, s.svcCtx)
|
||||
return l.CancelToken(in)
|
||||
}
|
||||
|
||||
// ValidationToken 驗證這個 Token 有沒有效
|
||||
func (s *TokenServiceServer) ValidationToken(ctx context.Context, in *app_cloudep_permission_server.ValidationTokenReq) (*app_cloudep_permission_server.ValidationTokenResp, error) {
|
||||
func (s *TokenServiceServer) ValidationToken(ctx context.Context, in *permission.ValidationTokenReq) (*permission.ValidationTokenResp, error) {
|
||||
l := tokenservicelogic.NewValidationTokenLogic(ctx, s.svcCtx)
|
||||
return l.ValidationToken(in)
|
||||
}
|
||||
|
||||
// CancelTokens 取消 Token 從UID 視角,以及 token id 視角出發, UID 登出,底下所有 Device ID 也要登出, Token ID 登出, 所有 UID + Device 都要登出
|
||||
func (s *TokenServiceServer) CancelTokens(ctx context.Context, in *app_cloudep_permission_server.DoTokenByUIDReq) (*app_cloudep_permission_server.OKResp, error) {
|
||||
func (s *TokenServiceServer) CancelTokens(ctx context.Context, in *permission.DoTokenByUIDReq) (*permission.OKResp, error) {
|
||||
l := tokenservicelogic.NewCancelTokensLogic(ctx, s.svcCtx)
|
||||
return l.CancelTokens(in)
|
||||
}
|
||||
|
||||
// CancelTokenByDeviceId 取消 Token, 從 Device 視角出發,可以選,登出這個Device 下所有 token ,登出這個Device 下指定token
|
||||
func (s *TokenServiceServer) CancelTokenByDeviceId(ctx context.Context, in *app_cloudep_permission_server.DoTokenByDeviceIDReq) (*app_cloudep_permission_server.OKResp, error) {
|
||||
func (s *TokenServiceServer) CancelTokenByDeviceId(ctx context.Context, in *permission.DoTokenByDeviceIDReq) (*permission.OKResp, error) {
|
||||
l := tokenservicelogic.NewCancelTokenByDeviceIdLogic(ctx, s.svcCtx)
|
||||
return l.CancelTokenByDeviceId(in)
|
||||
}
|
||||
|
||||
// GetUserTokensByDeviceId 取得目前所對應的 DeviceID 所存在的 Tokens
|
||||
func (s *TokenServiceServer) GetUserTokensByDeviceId(ctx context.Context, in *app_cloudep_permission_server.DoTokenByDeviceIDReq) (*app_cloudep_permission_server.Tokens, error) {
|
||||
func (s *TokenServiceServer) GetUserTokensByDeviceId(ctx context.Context, in *permission.DoTokenByDeviceIDReq) (*permission.Tokens, error) {
|
||||
l := tokenservicelogic.NewGetUserTokensByDeviceIdLogic(ctx, s.svcCtx)
|
||||
return l.GetUserTokensByDeviceId(in)
|
||||
}
|
||||
|
||||
// GetUserTokensByUid 取得目前所對應的 UID 所存在的 Tokens
|
||||
func (s *TokenServiceServer) GetUserTokensByUid(ctx context.Context, in *app_cloudep_permission_server.QueryTokenByUIDReq) (*app_cloudep_permission_server.Tokens, error) {
|
||||
func (s *TokenServiceServer) GetUserTokensByUid(ctx context.Context, in *permission.QueryTokenByUIDReq) (*permission.Tokens, error) {
|
||||
l := tokenservicelogic.NewGetUserTokensByUidLogic(ctx, s.svcCtx)
|
||||
return l.GetUserTokensByUid(in)
|
||||
}
|
||||
|
||||
// NewOneTimeToken 建立一次性使用,例如:RefreshToken
|
||||
func (s *TokenServiceServer) NewOneTimeToken(ctx context.Context, in *app_cloudep_permission_server.CreateOneTimeTokenReq) (*app_cloudep_permission_server.CreateOneTimeTokenResp, error) {
|
||||
func (s *TokenServiceServer) NewOneTimeToken(ctx context.Context, in *permission.CreateOneTimeTokenReq) (*permission.CreateOneTimeTokenResp, error) {
|
||||
l := tokenservicelogic.NewNewOneTimeTokenLogic(ctx, s.svcCtx)
|
||||
return l.NewOneTimeToken(in)
|
||||
}
|
||||
|
||||
// CancelOneTimeToken 取消一次性使用
|
||||
func (s *TokenServiceServer) CancelOneTimeToken(ctx context.Context, in *app_cloudep_permission_server.CancelOneTimeTokenReq) (*app_cloudep_permission_server.OKResp, error) {
|
||||
func (s *TokenServiceServer) CancelOneTimeToken(ctx context.Context, in *permission.CancelOneTimeTokenReq) (*permission.OKResp, error) {
|
||||
l := tokenservicelogic.NewCancelOneTimeTokenLogic(ctx, s.svcCtx)
|
||||
return l.CancelOneTimeToken(in)
|
||||
}
|
||||
|
|
|
@ -4,7 +4,7 @@ import (
|
|||
"flag"
|
||||
"fmt"
|
||||
|
||||
"code.30cm.net/digimon/app-cloudep-permission-server/gen_result/pb/app-cloudep-permission-server"
|
||||
"code.30cm.net/digimon/app-cloudep-permission-server/gen_result/pb/permission"
|
||||
"code.30cm.net/digimon/app-cloudep-permission-server/internal/config"
|
||||
tokenserviceServer "code.30cm.net/digimon/app-cloudep-permission-server/internal/server/tokenservice"
|
||||
"code.30cm.net/digimon/app-cloudep-permission-server/internal/svc"
|
||||
|
@ -26,7 +26,7 @@ func main() {
|
|||
ctx := svc.NewServiceContext(c)
|
||||
|
||||
s := zrpc.MustNewServer(c.RpcServerConf, func(grpcServer *grpc.Server) {
|
||||
app_cloudep_permission_server.RegisterTokenServiceServer(grpcServer, tokenserviceServer.NewTokenServiceServer(ctx))
|
||||
permission.RegisterTokenServiceServer(grpcServer, tokenserviceServer.NewTokenServiceServer(ctx))
|
||||
|
||||
if c.Mode == service.DevMode || c.Mode == service.TestMode {
|
||||
reflection.Register(grpcServer)
|
||||
|
|
|
@ -0,0 +1,8 @@
|
|||
package entity
|
||||
|
||||
import "github.com/golang-jwt/jwt/v4"
|
||||
|
||||
type Claims struct {
|
||||
jwt.RegisteredClaims
|
||||
Data interface{} `json:"data"`
|
||||
}
|
|
@ -1 +1,43 @@
|
|||
package domain
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
ers "code.30cm.net/digimon/library-go/errs"
|
||||
"code.30cm.net/digimon/library-go/errs/code"
|
||||
"github.com/zeromicro/go-zero/core/logx"
|
||||
)
|
||||
|
||||
const (
|
||||
TokenServerErrorCode = 1 + iota
|
||||
TokenServerRedisErrorCode
|
||||
TokenValidateErrorCode
|
||||
TokenClaimErrorCode
|
||||
TokenCreateErrorCode
|
||||
TokenRefreshErrorCode
|
||||
TokenCancelErrorCode
|
||||
TokensCancelErrorCode
|
||||
TokenGetErrorCode
|
||||
NewOneTokenErrorCode
|
||||
DelOneTokenErrorCode
|
||||
SendTooShortErrorCode
|
||||
SetForgetPasswordRedisErrorCode
|
||||
FailedToGetCorrectVerifyCode
|
||||
SendVerifyCodeRedisErrorCode
|
||||
GenerateVerifyCodeRedisErrorCode
|
||||
FailedToCheckVerifyCode
|
||||
AccountPlatformNotCorrectErrorCode
|
||||
)
|
||||
|
||||
func TokenError(ec ers.ErrorCode, s ...string) *ers.LibError {
|
||||
return ers.NewError(code.CloudEPPermission, code.SigAndPayloadNotMatched, ec.ToUint32(), fmt.Sprintf("token create error: %s", strings.Join(s, " ")))
|
||||
}
|
||||
|
||||
func TokenErrorL(ec ers.ErrorCode,
|
||||
l logx.Logger, filed []logx.LogField, s ...string) *ers.LibError {
|
||||
e := TokenError(ec, s...)
|
||||
l.WithCallerSkip(1).WithFields(filed...).Error(e.Error())
|
||||
|
||||
return e
|
||||
}
|
||||
|
|
|
@ -1,9 +1,10 @@
|
|||
package repository
|
||||
|
||||
import (
|
||||
"code.30cm.net/digimon/app-cloudep-permission-server/pkg/domain/entity"
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"code.30cm.net/digimon/app-cloudep-permission-server/pkg/domain/entity"
|
||||
)
|
||||
|
||||
// TokenRepo 管理Token
|
||||
|
|
|
@ -0,0 +1,38 @@
|
|||
package token
|
||||
|
||||
type Additional string
|
||||
|
||||
func (a Additional) String() string {
|
||||
return string(a)
|
||||
}
|
||||
|
||||
const (
|
||||
ID Additional = "id"
|
||||
Role Additional = "role"
|
||||
Device Additional = "device"
|
||||
UID Additional = "uid"
|
||||
Account Additional = "account"
|
||||
Scope Additional = "scope"
|
||||
Type Additional = "token_type"
|
||||
)
|
||||
|
||||
// 定義一個集合存放所有合法的 Additional Keys
|
||||
var validAdditionalKeys = map[Additional]struct{}{
|
||||
ID: {},
|
||||
Role: {},
|
||||
Device: {},
|
||||
UID: {},
|
||||
Account: {},
|
||||
Scope: {},
|
||||
Type: {},
|
||||
}
|
||||
|
||||
// IsValidAdditional 檢查是否是有效的 Additional Key
|
||||
func IsValidAdditional(key Additional) bool {
|
||||
_, exists := validAdditionalKeys[key]
|
||||
return exists
|
||||
}
|
||||
|
||||
const (
|
||||
Issuer = "permission"
|
||||
)
|
|
@ -0,0 +1,7 @@
|
|||
package token
|
||||
|
||||
type TScope string
|
||||
|
||||
func (s *TScope) ToString() string {
|
||||
return string(*s)
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
package token
|
||||
|
||||
type VerifyType string
|
||||
|
||||
func (t *VerifyType) ToString() string {
|
||||
return string(*t)
|
||||
}
|
||||
|
||||
const (
|
||||
Bearer VerifyType = "Bearer"
|
||||
)
|
|
@ -0,0 +1,10 @@
|
|||
package usecase
|
||||
|
||||
import "code.30cm.net/digimon/app-cloudep-permission-server/pkg/domain/token"
|
||||
|
||||
// Additional 系統在 Token 當中的附加資訊
|
||||
type Additional interface {
|
||||
Set(key token.Additional, val string)
|
||||
Get(key token.Additional) string
|
||||
GetAll() map[string]string
|
||||
}
|
|
@ -1,40 +1,92 @@
|
|||
package usecase
|
||||
|
||||
import "context"
|
||||
import (
|
||||
"context"
|
||||
|
||||
"code.30cm.net/digimon/app-cloudep-permission-server/pkg/domain/entity"
|
||||
"github.com/golang-jwt/jwt/v4"
|
||||
)
|
||||
|
||||
type TokenUseCase interface {
|
||||
// NewToken 創建新 Token,通常為 Access Token
|
||||
NewToken(ctx context.Context, req AuthorizationReq) (TokenResp, error)
|
||||
// RefreshToken 刷新目前的 Token,包括一次性 Token
|
||||
RefreshToken(ctx context.Context, req RefreshTokenReq) (RefreshTokenResp, error)
|
||||
// CancelToken 取消 Token,包括取消其關聯的 One-Time Token
|
||||
CancelToken(ctx context.Context, req CancelTokenReq) error
|
||||
// ValidationToken 驗證 Token 是否有效
|
||||
ValidationToken(ctx context.Context, req ValidationTokenReq) (ValidationTokenResp, error)
|
||||
// CancelTokens 根據 UID 或 Token ID 取消所有相關 Token,通常在用戶登出時使用
|
||||
CancelTokens(ctx context.Context, req DoTokenByUIDReq) error
|
||||
// CancelTokenByDeviceID 根據 Device ID 取消所有相關的 Token
|
||||
CancelTokenByDeviceID(ctx context.Context, req DoTokenByDeviceIDReq) error
|
||||
// GetUserTokensByDeviceID 根據 Device ID 獲取所有 Token
|
||||
GetUserTokensByDeviceID(ctx context.Context, req DoTokenByDeviceIDReq) ([]*TokenResp, error)
|
||||
// GetUserTokensByUID 根據 UID 獲取所有 Token
|
||||
GetUserTokensByUID(ctx context.Context, req QueryTokenByUIDReq) ([]*TokenResp, error)
|
||||
// NewOneTimeToken 創建一次性 Token,例如 Refresh Token
|
||||
NewOneTimeToken(ctx context.Context, req CreateOneTimeTokenReq) (CreateOneTimeTokenResp, error)
|
||||
// CancelOneTimeToken 取消一次性 Token
|
||||
CancelOneTimeToken(ctx context.Context, req CancelOneTimeTokenReq) error
|
||||
ParseClaims
|
||||
// GenerateAccessToken 產生新的 Access Token
|
||||
GenerateAccessToken(ctx context.Context, req GenerateTokenRequest) (AccessTokenResponse, error)
|
||||
// RefreshAccessToken 使用 Refresh Token 更新 Access Token(刷新令牌)
|
||||
RefreshAccessToken(ctx context.Context, req RefreshTokenRequest) (RefreshTokenResponse, error)
|
||||
// RevokeToken 撤銷單個 Token
|
||||
RevokeToken(ctx context.Context, req TokenRequest) error
|
||||
// VerifyToken 驗證 Token 是否有效
|
||||
VerifyToken(ctx context.Context, req TokenRequest) (VerifyTokenResponse, error)
|
||||
// RevokeTokensByUID 根據 UID 撤銷所有 Token
|
||||
RevokeTokensByUID(ctx context.Context, req RevokeTokensByUIDRequest) error
|
||||
// RevokeTokensByDeviceID 根據 Device ID 取消所有相關的 Token
|
||||
RevokeTokensByDeviceID(ctx context.Context, deviceID string) error
|
||||
// GetUserTokensByDeviceID 根據 Device ID 獲取所有 AccessToken
|
||||
GetUserTokensByDeviceID(ctx context.Context, deviceID string) ([]*AccessTokenResponse, error)
|
||||
// GetUserTokensByUID 根據 UID 獲取所有 AccessToken
|
||||
GetUserTokensByUID(ctx context.Context, uid string) ([]*AccessTokenResponse, error)
|
||||
// ReadTokenBasicData 檢查Token 帶的資料
|
||||
ReadTokenBasicData(ctx context.Context, token string) (map[string]string, error)
|
||||
ReadTokenBasicData(ctx context.Context, token string) (Additional, error)
|
||||
}
|
||||
|
||||
// AuthorizationReq 定義授權請求的結構
|
||||
type AuthorizationReq struct {
|
||||
GrantType string `json:"grant_type"` // 授權類型
|
||||
type ParseClaims interface {
|
||||
// CreateAccessToken 建立 access token
|
||||
CreateAccessToken(token entity.Token, data any, secretKey string) (string, error)
|
||||
// CreateRefreshToken 建立 RefreshToken
|
||||
CreateRefreshToken(accessToken string) string
|
||||
// ParseJWTClaimsByAccessToken 使用Access Token 解析出 JWT 資訊
|
||||
ParseJWTClaimsByAccessToken(accessToken string, secret string, validate bool) (jwt.MapClaims, error)
|
||||
// ParseSystemClaimsByAccessToken 使用Access Token 解析出 系統資訊
|
||||
ParseSystemClaimsByAccessToken(accessToken string, secret string, validate bool) (map[string]string, error)
|
||||
}
|
||||
|
||||
// GenerateTokenRequest 定義授權請求的結構
|
||||
type GenerateTokenRequest struct {
|
||||
TokenType string `json:"token_type"` // 告訴前端Token 類型
|
||||
DeviceID string `json:"device_id"` // 設備 ID
|
||||
Scope string `json:"scope"` // 授權範圍
|
||||
Data map[string]string `json:"data"` // 附加數據
|
||||
Expires int64 `json:"expires"` // 過期時間(秒)
|
||||
IsRefreshToken bool `json:"is_refresh_token"` // 是否為刷新令牌
|
||||
Expires int64 `json:"expires"` // 指定過期時間 UnixNano UTC 時間(沒給 = now 加設定秒數)
|
||||
RefreshExpires int64 `json:"refresh_expires"` // 指定過期時間 UnixNano UTC 時間(沒給 = now 加設定秒數)
|
||||
Role string `json:"role"` // 是否為刷新令牌
|
||||
Account string `json:"account"` // 登入時的帳號
|
||||
Account string `json:"account"` // 登入時用的帳號
|
||||
UID string `json:"uid"` // 使用者在系統中的帳號
|
||||
Data map[string]string `json:"data"` // 附加數據 -> 不在上面的以後要額外放進來的
|
||||
}
|
||||
|
||||
// AccessTokenResponse 定義訪問令牌響應的結構
|
||||
type AccessTokenResponse struct {
|
||||
AccessToken string `json:"access_token"` // 訪問令牌
|
||||
ExpiresIn int64 `json:"expires_in"` // 過期時間 UnixNano UTC 時間
|
||||
RefreshToken string `json:"refresh_token"` // 刷新令牌
|
||||
}
|
||||
|
||||
// RefreshTokenRequest 更新 Token 的請求
|
||||
type RefreshTokenRequest struct {
|
||||
Token string `json:"token"` // 令牌
|
||||
Scope string `json:"scope"` // 授權範圍
|
||||
Expires int64 `json:"expires"` // 指定過期時間 UnixNano UTC 時間(沒給 = now 加設定秒數)
|
||||
RefreshExpires int64 `json:"refresh_expires"` // 指定過期時間 UnixNano UTC 時間(沒給 = now 加設定秒數)
|
||||
DeviceID string `json:"device_id"` // 設備 ID
|
||||
}
|
||||
|
||||
// RefreshTokenResponse 更新令牌的響應
|
||||
type RefreshTokenResponse struct {
|
||||
AccessToken string `json:"token"` // 新的訪問令牌
|
||||
RefreshToken string `json:"refresh_token"` // 更新令牌
|
||||
ExpiresIn int64 `json:"expires_in"` // 過期時間(秒)
|
||||
TokenType string `json:"token_type"` // 令牌類型
|
||||
}
|
||||
|
||||
type TokenRequest struct {
|
||||
Token string `json:"token"` // 需要註銷的令牌
|
||||
}
|
||||
|
||||
type VerifyTokenResponse struct {
|
||||
Token entity.Token `json:"token"` // Token 詳情
|
||||
Data map[string]string `json:"data"` // 附加資料
|
||||
}
|
||||
|
||||
type RevokeTokensByUIDRequest struct {
|
||||
IDs []string `json:"ids"` // Token ID 列表
|
||||
UID string `json:"uid"` // 用戶 ID
|
||||
}
|
||||
|
|
|
@ -0,0 +1,491 @@
|
|||
// Code generated by MockGen. DO NOT EDIT.
|
||||
// Source: ./pkg/domain/repository/token.go
|
||||
//
|
||||
// Generated by this command:
|
||||
//
|
||||
// mockgen -source=./pkg/domain/repository/token.go -destination=./pkg/mock/repository/token.go -package=mock
|
||||
//
|
||||
|
||||
// Package mock is a generated GoMock package.
|
||||
package mock
|
||||
|
||||
import (
|
||||
context "context"
|
||||
reflect "reflect"
|
||||
time "time"
|
||||
|
||||
entity "code.30cm.net/digimon/app-cloudep-permission-server/pkg/domain/entity"
|
||||
gomock "go.uber.org/mock/gomock"
|
||||
)
|
||||
|
||||
// MockTokenRepo is a mock of TokenRepo interface.
|
||||
type MockTokenRepo struct {
|
||||
ctrl *gomock.Controller
|
||||
recorder *MockTokenRepoMockRecorder
|
||||
isgomock struct{}
|
||||
}
|
||||
|
||||
// MockTokenRepoMockRecorder is the mock recorder for MockTokenRepo.
|
||||
type MockTokenRepoMockRecorder struct {
|
||||
mock *MockTokenRepo
|
||||
}
|
||||
|
||||
// NewMockTokenRepo creates a new mock instance.
|
||||
func NewMockTokenRepo(ctrl *gomock.Controller) *MockTokenRepo {
|
||||
mock := &MockTokenRepo{ctrl: ctrl}
|
||||
mock.recorder = &MockTokenRepoMockRecorder{mock}
|
||||
return mock
|
||||
}
|
||||
|
||||
// EXPECT returns an object that allows the caller to indicate expected use.
|
||||
func (m *MockTokenRepo) EXPECT() *MockTokenRepoMockRecorder {
|
||||
return m.recorder
|
||||
}
|
||||
|
||||
// Create mocks base method.
|
||||
func (m *MockTokenRepo) Create(ctx context.Context, token entity.Token) error {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "Create", ctx, token)
|
||||
ret0, _ := ret[0].(error)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// Create indicates an expected call of Create.
|
||||
func (mr *MockTokenRepoMockRecorder) Create(ctx, token any) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Create", reflect.TypeOf((*MockTokenRepo)(nil).Create), ctx, token)
|
||||
}
|
||||
|
||||
// CreateOneTimeToken mocks base method.
|
||||
func (m *MockTokenRepo) CreateOneTimeToken(ctx context.Context, key string, ticket entity.Ticket, et time.Duration) error {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "CreateOneTimeToken", ctx, key, ticket, et)
|
||||
ret0, _ := ret[0].(error)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// CreateOneTimeToken indicates an expected call of CreateOneTimeToken.
|
||||
func (mr *MockTokenRepoMockRecorder) CreateOneTimeToken(ctx, key, ticket, et any) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateOneTimeToken", reflect.TypeOf((*MockTokenRepo)(nil).CreateOneTimeToken), ctx, key, ticket, et)
|
||||
}
|
||||
|
||||
// Delete mocks base method.
|
||||
func (m *MockTokenRepo) Delete(ctx context.Context, token entity.Token) error {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "Delete", ctx, token)
|
||||
ret0, _ := ret[0].(error)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// Delete indicates an expected call of Delete.
|
||||
func (mr *MockTokenRepoMockRecorder) Delete(ctx, token any) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Delete", reflect.TypeOf((*MockTokenRepo)(nil).Delete), ctx, token)
|
||||
}
|
||||
|
||||
// DeleteAccessTokenByID mocks base method.
|
||||
func (m *MockTokenRepo) DeleteAccessTokenByID(ctx context.Context, ids []string) error {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "DeleteAccessTokenByID", ctx, ids)
|
||||
ret0, _ := ret[0].(error)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// DeleteAccessTokenByID indicates an expected call of DeleteAccessTokenByID.
|
||||
func (mr *MockTokenRepoMockRecorder) DeleteAccessTokenByID(ctx, ids any) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteAccessTokenByID", reflect.TypeOf((*MockTokenRepo)(nil).DeleteAccessTokenByID), ctx, ids)
|
||||
}
|
||||
|
||||
// DeleteAccessTokensByDeviceID mocks base method.
|
||||
func (m *MockTokenRepo) DeleteAccessTokensByDeviceID(ctx context.Context, deviceID string) error {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "DeleteAccessTokensByDeviceID", ctx, deviceID)
|
||||
ret0, _ := ret[0].(error)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// DeleteAccessTokensByDeviceID indicates an expected call of DeleteAccessTokensByDeviceID.
|
||||
func (mr *MockTokenRepoMockRecorder) DeleteAccessTokensByDeviceID(ctx, deviceID any) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteAccessTokensByDeviceID", reflect.TypeOf((*MockTokenRepo)(nil).DeleteAccessTokensByDeviceID), ctx, deviceID)
|
||||
}
|
||||
|
||||
// DeleteAccessTokensByUID mocks base method.
|
||||
func (m *MockTokenRepo) DeleteAccessTokensByUID(ctx context.Context, uid string) error {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "DeleteAccessTokensByUID", ctx, uid)
|
||||
ret0, _ := ret[0].(error)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// DeleteAccessTokensByUID indicates an expected call of DeleteAccessTokensByUID.
|
||||
func (mr *MockTokenRepoMockRecorder) DeleteAccessTokensByUID(ctx, uid any) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteAccessTokensByUID", reflect.TypeOf((*MockTokenRepo)(nil).DeleteAccessTokensByUID), ctx, uid)
|
||||
}
|
||||
|
||||
// DeleteOneTimeToken mocks base method.
|
||||
func (m *MockTokenRepo) DeleteOneTimeToken(ctx context.Context, ids []string, tokens []entity.Token) error {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "DeleteOneTimeToken", ctx, ids, tokens)
|
||||
ret0, _ := ret[0].(error)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// DeleteOneTimeToken indicates an expected call of DeleteOneTimeToken.
|
||||
func (mr *MockTokenRepoMockRecorder) DeleteOneTimeToken(ctx, ids, tokens any) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteOneTimeToken", reflect.TypeOf((*MockTokenRepo)(nil).DeleteOneTimeToken), ctx, ids, tokens)
|
||||
}
|
||||
|
||||
// GetAccessTokenByID mocks base method.
|
||||
func (m *MockTokenRepo) GetAccessTokenByID(ctx context.Context, id string) (entity.Token, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "GetAccessTokenByID", ctx, id)
|
||||
ret0, _ := ret[0].(entity.Token)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// GetAccessTokenByID indicates an expected call of GetAccessTokenByID.
|
||||
func (mr *MockTokenRepoMockRecorder) GetAccessTokenByID(ctx, id any) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetAccessTokenByID", reflect.TypeOf((*MockTokenRepo)(nil).GetAccessTokenByID), ctx, id)
|
||||
}
|
||||
|
||||
// GetAccessTokenByOneTimeToken mocks base method.
|
||||
func (m *MockTokenRepo) GetAccessTokenByOneTimeToken(ctx context.Context, oneTimeToken string) (entity.Token, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "GetAccessTokenByOneTimeToken", ctx, oneTimeToken)
|
||||
ret0, _ := ret[0].(entity.Token)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// GetAccessTokenByOneTimeToken indicates an expected call of GetAccessTokenByOneTimeToken.
|
||||
func (mr *MockTokenRepoMockRecorder) GetAccessTokenByOneTimeToken(ctx, oneTimeToken any) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetAccessTokenByOneTimeToken", reflect.TypeOf((*MockTokenRepo)(nil).GetAccessTokenByOneTimeToken), ctx, oneTimeToken)
|
||||
}
|
||||
|
||||
// GetAccessTokenCountByDeviceID mocks base method.
|
||||
func (m *MockTokenRepo) GetAccessTokenCountByDeviceID(ctx context.Context, deviceID string) (int, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "GetAccessTokenCountByDeviceID", ctx, deviceID)
|
||||
ret0, _ := ret[0].(int)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// GetAccessTokenCountByDeviceID indicates an expected call of GetAccessTokenCountByDeviceID.
|
||||
func (mr *MockTokenRepoMockRecorder) GetAccessTokenCountByDeviceID(ctx, deviceID any) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetAccessTokenCountByDeviceID", reflect.TypeOf((*MockTokenRepo)(nil).GetAccessTokenCountByDeviceID), ctx, deviceID)
|
||||
}
|
||||
|
||||
// GetAccessTokenCountByUID mocks base method.
|
||||
func (m *MockTokenRepo) GetAccessTokenCountByUID(ctx context.Context, uid string) (int, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "GetAccessTokenCountByUID", ctx, uid)
|
||||
ret0, _ := ret[0].(int)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// GetAccessTokenCountByUID indicates an expected call of GetAccessTokenCountByUID.
|
||||
func (mr *MockTokenRepoMockRecorder) GetAccessTokenCountByUID(ctx, uid any) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetAccessTokenCountByUID", reflect.TypeOf((*MockTokenRepo)(nil).GetAccessTokenCountByUID), ctx, uid)
|
||||
}
|
||||
|
||||
// GetAccessTokensByDeviceID mocks base method.
|
||||
func (m *MockTokenRepo) GetAccessTokensByDeviceID(ctx context.Context, deviceID string) ([]entity.Token, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "GetAccessTokensByDeviceID", ctx, deviceID)
|
||||
ret0, _ := ret[0].([]entity.Token)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// GetAccessTokensByDeviceID indicates an expected call of GetAccessTokensByDeviceID.
|
||||
func (mr *MockTokenRepoMockRecorder) GetAccessTokensByDeviceID(ctx, deviceID any) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetAccessTokensByDeviceID", reflect.TypeOf((*MockTokenRepo)(nil).GetAccessTokensByDeviceID), ctx, deviceID)
|
||||
}
|
||||
|
||||
// GetAccessTokensByUID mocks base method.
|
||||
func (m *MockTokenRepo) GetAccessTokensByUID(ctx context.Context, uid string) ([]entity.Token, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "GetAccessTokensByUID", ctx, uid)
|
||||
ret0, _ := ret[0].([]entity.Token)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// GetAccessTokensByUID indicates an expected call of GetAccessTokensByUID.
|
||||
func (mr *MockTokenRepoMockRecorder) GetAccessTokensByUID(ctx, uid any) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetAccessTokensByUID", reflect.TypeOf((*MockTokenRepo)(nil).GetAccessTokensByUID), ctx, uid)
|
||||
}
|
||||
|
||||
// MockCreate is a mock of Create interface.
|
||||
type MockCreate struct {
|
||||
ctrl *gomock.Controller
|
||||
recorder *MockCreateMockRecorder
|
||||
isgomock struct{}
|
||||
}
|
||||
|
||||
// MockCreateMockRecorder is the mock recorder for MockCreate.
|
||||
type MockCreateMockRecorder struct {
|
||||
mock *MockCreate
|
||||
}
|
||||
|
||||
// NewMockCreate creates a new mock instance.
|
||||
func NewMockCreate(ctrl *gomock.Controller) *MockCreate {
|
||||
mock := &MockCreate{ctrl: ctrl}
|
||||
mock.recorder = &MockCreateMockRecorder{mock}
|
||||
return mock
|
||||
}
|
||||
|
||||
// EXPECT returns an object that allows the caller to indicate expected use.
|
||||
func (m *MockCreate) EXPECT() *MockCreateMockRecorder {
|
||||
return m.recorder
|
||||
}
|
||||
|
||||
// Create mocks base method.
|
||||
func (m *MockCreate) Create(ctx context.Context, token entity.Token) error {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "Create", ctx, token)
|
||||
ret0, _ := ret[0].(error)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// Create indicates an expected call of Create.
|
||||
func (mr *MockCreateMockRecorder) Create(ctx, token any) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Create", reflect.TypeOf((*MockCreate)(nil).Create), ctx, token)
|
||||
}
|
||||
|
||||
// CreateOneTimeToken mocks base method.
|
||||
func (m *MockCreate) CreateOneTimeToken(ctx context.Context, key string, ticket entity.Ticket, et time.Duration) error {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "CreateOneTimeToken", ctx, key, ticket, et)
|
||||
ret0, _ := ret[0].(error)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// CreateOneTimeToken indicates an expected call of CreateOneTimeToken.
|
||||
func (mr *MockCreateMockRecorder) CreateOneTimeToken(ctx, key, ticket, et any) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateOneTimeToken", reflect.TypeOf((*MockCreate)(nil).CreateOneTimeToken), ctx, key, ticket, et)
|
||||
}
|
||||
|
||||
// MockGet is a mock of Get interface.
|
||||
type MockGet struct {
|
||||
ctrl *gomock.Controller
|
||||
recorder *MockGetMockRecorder
|
||||
isgomock struct{}
|
||||
}
|
||||
|
||||
// MockGetMockRecorder is the mock recorder for MockGet.
|
||||
type MockGetMockRecorder struct {
|
||||
mock *MockGet
|
||||
}
|
||||
|
||||
// NewMockGet creates a new mock instance.
|
||||
func NewMockGet(ctrl *gomock.Controller) *MockGet {
|
||||
mock := &MockGet{ctrl: ctrl}
|
||||
mock.recorder = &MockGetMockRecorder{mock}
|
||||
return mock
|
||||
}
|
||||
|
||||
// EXPECT returns an object that allows the caller to indicate expected use.
|
||||
func (m *MockGet) EXPECT() *MockGetMockRecorder {
|
||||
return m.recorder
|
||||
}
|
||||
|
||||
// GetAccessTokenByID mocks base method.
|
||||
func (m *MockGet) GetAccessTokenByID(ctx context.Context, id string) (entity.Token, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "GetAccessTokenByID", ctx, id)
|
||||
ret0, _ := ret[0].(entity.Token)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// GetAccessTokenByID indicates an expected call of GetAccessTokenByID.
|
||||
func (mr *MockGetMockRecorder) GetAccessTokenByID(ctx, id any) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetAccessTokenByID", reflect.TypeOf((*MockGet)(nil).GetAccessTokenByID), ctx, id)
|
||||
}
|
||||
|
||||
// GetAccessTokenByOneTimeToken mocks base method.
|
||||
func (m *MockGet) GetAccessTokenByOneTimeToken(ctx context.Context, oneTimeToken string) (entity.Token, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "GetAccessTokenByOneTimeToken", ctx, oneTimeToken)
|
||||
ret0, _ := ret[0].(entity.Token)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// GetAccessTokenByOneTimeToken indicates an expected call of GetAccessTokenByOneTimeToken.
|
||||
func (mr *MockGetMockRecorder) GetAccessTokenByOneTimeToken(ctx, oneTimeToken any) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetAccessTokenByOneTimeToken", reflect.TypeOf((*MockGet)(nil).GetAccessTokenByOneTimeToken), ctx, oneTimeToken)
|
||||
}
|
||||
|
||||
// GetAccessTokenCountByDeviceID mocks base method.
|
||||
func (m *MockGet) GetAccessTokenCountByDeviceID(ctx context.Context, deviceID string) (int, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "GetAccessTokenCountByDeviceID", ctx, deviceID)
|
||||
ret0, _ := ret[0].(int)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// GetAccessTokenCountByDeviceID indicates an expected call of GetAccessTokenCountByDeviceID.
|
||||
func (mr *MockGetMockRecorder) GetAccessTokenCountByDeviceID(ctx, deviceID any) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetAccessTokenCountByDeviceID", reflect.TypeOf((*MockGet)(nil).GetAccessTokenCountByDeviceID), ctx, deviceID)
|
||||
}
|
||||
|
||||
// GetAccessTokenCountByUID mocks base method.
|
||||
func (m *MockGet) GetAccessTokenCountByUID(ctx context.Context, uid string) (int, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "GetAccessTokenCountByUID", ctx, uid)
|
||||
ret0, _ := ret[0].(int)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// GetAccessTokenCountByUID indicates an expected call of GetAccessTokenCountByUID.
|
||||
func (mr *MockGetMockRecorder) GetAccessTokenCountByUID(ctx, uid any) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetAccessTokenCountByUID", reflect.TypeOf((*MockGet)(nil).GetAccessTokenCountByUID), ctx, uid)
|
||||
}
|
||||
|
||||
// GetAccessTokensByDeviceID mocks base method.
|
||||
func (m *MockGet) GetAccessTokensByDeviceID(ctx context.Context, deviceID string) ([]entity.Token, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "GetAccessTokensByDeviceID", ctx, deviceID)
|
||||
ret0, _ := ret[0].([]entity.Token)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// GetAccessTokensByDeviceID indicates an expected call of GetAccessTokensByDeviceID.
|
||||
func (mr *MockGetMockRecorder) GetAccessTokensByDeviceID(ctx, deviceID any) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetAccessTokensByDeviceID", reflect.TypeOf((*MockGet)(nil).GetAccessTokensByDeviceID), ctx, deviceID)
|
||||
}
|
||||
|
||||
// GetAccessTokensByUID mocks base method.
|
||||
func (m *MockGet) GetAccessTokensByUID(ctx context.Context, uid string) ([]entity.Token, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "GetAccessTokensByUID", ctx, uid)
|
||||
ret0, _ := ret[0].([]entity.Token)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// GetAccessTokensByUID indicates an expected call of GetAccessTokensByUID.
|
||||
func (mr *MockGetMockRecorder) GetAccessTokensByUID(ctx, uid any) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetAccessTokensByUID", reflect.TypeOf((*MockGet)(nil).GetAccessTokensByUID), ctx, uid)
|
||||
}
|
||||
|
||||
// MockDelete is a mock of Delete interface.
|
||||
type MockDelete struct {
|
||||
ctrl *gomock.Controller
|
||||
recorder *MockDeleteMockRecorder
|
||||
isgomock struct{}
|
||||
}
|
||||
|
||||
// MockDeleteMockRecorder is the mock recorder for MockDelete.
|
||||
type MockDeleteMockRecorder struct {
|
||||
mock *MockDelete
|
||||
}
|
||||
|
||||
// NewMockDelete creates a new mock instance.
|
||||
func NewMockDelete(ctrl *gomock.Controller) *MockDelete {
|
||||
mock := &MockDelete{ctrl: ctrl}
|
||||
mock.recorder = &MockDeleteMockRecorder{mock}
|
||||
return mock
|
||||
}
|
||||
|
||||
// EXPECT returns an object that allows the caller to indicate expected use.
|
||||
func (m *MockDelete) EXPECT() *MockDeleteMockRecorder {
|
||||
return m.recorder
|
||||
}
|
||||
|
||||
// Delete mocks base method.
|
||||
func (m *MockDelete) Delete(ctx context.Context, token entity.Token) error {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "Delete", ctx, token)
|
||||
ret0, _ := ret[0].(error)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// Delete indicates an expected call of Delete.
|
||||
func (mr *MockDeleteMockRecorder) Delete(ctx, token any) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Delete", reflect.TypeOf((*MockDelete)(nil).Delete), ctx, token)
|
||||
}
|
||||
|
||||
// DeleteAccessTokenByID mocks base method.
|
||||
func (m *MockDelete) DeleteAccessTokenByID(ctx context.Context, ids []string) error {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "DeleteAccessTokenByID", ctx, ids)
|
||||
ret0, _ := ret[0].(error)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// DeleteAccessTokenByID indicates an expected call of DeleteAccessTokenByID.
|
||||
func (mr *MockDeleteMockRecorder) DeleteAccessTokenByID(ctx, ids any) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteAccessTokenByID", reflect.TypeOf((*MockDelete)(nil).DeleteAccessTokenByID), ctx, ids)
|
||||
}
|
||||
|
||||
// DeleteAccessTokensByDeviceID mocks base method.
|
||||
func (m *MockDelete) DeleteAccessTokensByDeviceID(ctx context.Context, deviceID string) error {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "DeleteAccessTokensByDeviceID", ctx, deviceID)
|
||||
ret0, _ := ret[0].(error)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// DeleteAccessTokensByDeviceID indicates an expected call of DeleteAccessTokensByDeviceID.
|
||||
func (mr *MockDeleteMockRecorder) DeleteAccessTokensByDeviceID(ctx, deviceID any) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteAccessTokensByDeviceID", reflect.TypeOf((*MockDelete)(nil).DeleteAccessTokensByDeviceID), ctx, deviceID)
|
||||
}
|
||||
|
||||
// DeleteAccessTokensByUID mocks base method.
|
||||
func (m *MockDelete) DeleteAccessTokensByUID(ctx context.Context, uid string) error {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "DeleteAccessTokensByUID", ctx, uid)
|
||||
ret0, _ := ret[0].(error)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// DeleteAccessTokensByUID indicates an expected call of DeleteAccessTokensByUID.
|
||||
func (mr *MockDeleteMockRecorder) DeleteAccessTokensByUID(ctx, uid any) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteAccessTokensByUID", reflect.TypeOf((*MockDelete)(nil).DeleteAccessTokensByUID), ctx, uid)
|
||||
}
|
||||
|
||||
// DeleteOneTimeToken mocks base method.
|
||||
func (m *MockDelete) DeleteOneTimeToken(ctx context.Context, ids []string, tokens []entity.Token) error {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "DeleteOneTimeToken", ctx, ids, tokens)
|
||||
ret0, _ := ret[0].(error)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// DeleteOneTimeToken indicates an expected call of DeleteOneTimeToken.
|
||||
func (mr *MockDeleteMockRecorder) DeleteOneTimeToken(ctx, ids, tokens any) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteOneTimeToken", reflect.TypeOf((*MockDelete)(nil).DeleteOneTimeToken), ctx, ids, tokens)
|
||||
}
|
|
@ -1,15 +1,16 @@
|
|||
package repository
|
||||
|
||||
import (
|
||||
"code.30cm.net/digimon/app-cloudep-permission-server/pkg/domain"
|
||||
"code.30cm.net/digimon/app-cloudep-permission-server/pkg/domain/entity"
|
||||
"code.30cm.net/digimon/app-cloudep-permission-server/pkg/domain/repository"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/zeromicro/go-zero/core/stores/redis"
|
||||
"time"
|
||||
|
||||
"code.30cm.net/digimon/app-cloudep-permission-server/pkg/domain"
|
||||
"code.30cm.net/digimon/app-cloudep-permission-server/pkg/domain/entity"
|
||||
"code.30cm.net/digimon/app-cloudep-permission-server/pkg/domain/repository"
|
||||
"github.com/zeromicro/go-zero/core/stores/redis"
|
||||
)
|
||||
|
||||
// TokenRepositoryParam token 需要的參數
|
||||
|
|
|
@ -1,16 +1,17 @@
|
|||
package repository
|
||||
|
||||
import (
|
||||
"code.30cm.net/digimon/app-cloudep-permission-server/pkg/domain"
|
||||
"code.30cm.net/digimon/app-cloudep-permission-server/pkg/domain/entity"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"code.30cm.net/digimon/app-cloudep-permission-server/pkg/domain"
|
||||
"code.30cm.net/digimon/app-cloudep-permission-server/pkg/domain/entity"
|
||||
"github.com/alicebob/miniredis/v2"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/zeromicro/go-zero/core/stores/redis"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func setupMiniRedis() (*miniredis.Miniredis, *redis.Redis) {
|
||||
|
|
|
@ -0,0 +1,35 @@
|
|||
package usecase
|
||||
|
||||
import (
|
||||
"code.30cm.net/digimon/app-cloudep-permission-server/pkg/domain/token"
|
||||
"code.30cm.net/digimon/app-cloudep-permission-server/pkg/domain/usecase"
|
||||
)
|
||||
|
||||
// additional 實作 TokenClaims 介面
|
||||
type additional struct {
|
||||
additional map[string]string
|
||||
}
|
||||
|
||||
func (use *additional) GetAll() map[string]string {
|
||||
return use.additional
|
||||
}
|
||||
|
||||
func (use *additional) Set(key token.Additional, val string) {
|
||||
use.additional[key.String()] = val
|
||||
}
|
||||
|
||||
func (use *additional) Get(additional token.Additional) string {
|
||||
value, ok := use.additional[additional.String()]
|
||||
if !ok {
|
||||
return ""
|
||||
}
|
||||
|
||||
return value
|
||||
}
|
||||
|
||||
// NewAdditional 創建一個新的 tokenClaims 實例
|
||||
func NewAdditional(data map[string]string) usecase.Additional {
|
||||
return &additional{
|
||||
additional: data,
|
||||
}
|
||||
}
|
|
@ -0,0 +1,64 @@
|
|||
package usecase
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"code.30cm.net/digimon/app-cloudep-permission-server/pkg/domain/token"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestAdditional_SetAndGet(t *testing.T) {
|
||||
// 初始化 additional
|
||||
additional := NewAdditional(map[string]string{})
|
||||
|
||||
// 測試 Set() 只允許有效 Key
|
||||
validCases := map[token.Additional]string{
|
||||
token.ID: "12345",
|
||||
token.Role: "admin",
|
||||
token.Device: "device-001",
|
||||
token.UID: "user-999",
|
||||
token.Account: "test@example.com",
|
||||
token.Scope: "read:write",
|
||||
}
|
||||
|
||||
// 測試有效 Key
|
||||
for key, val := range validCases {
|
||||
additional.Set(key, val)
|
||||
assert.Equal(t, val, additional.Get(key), "Set/Get for key: "+key.String())
|
||||
}
|
||||
|
||||
// 測試 key 未設定時應回傳空字串
|
||||
assert.Equal(t, "", additional.Get("non-existent-key"))
|
||||
}
|
||||
|
||||
func TestIsValidAdditional(t *testing.T) {
|
||||
// 測試合法的 keys
|
||||
assert.True(t, token.IsValidAdditional(token.ID))
|
||||
assert.True(t, token.IsValidAdditional(token.Role))
|
||||
assert.True(t, token.IsValidAdditional(token.Device))
|
||||
assert.True(t, token.IsValidAdditional(token.UID))
|
||||
assert.True(t, token.IsValidAdditional(token.Account))
|
||||
assert.True(t, token.IsValidAdditional(token.Scope))
|
||||
|
||||
// 測試不合法的 keys
|
||||
assert.False(t, token.IsValidAdditional(token.Additional("unknown")))
|
||||
assert.False(t, token.IsValidAdditional(token.Additional("random")))
|
||||
assert.False(t, token.IsValidAdditional(token.Additional("invalid-key")))
|
||||
}
|
||||
|
||||
func TestIGetAll(t *testing.T) {
|
||||
validCases := map[string]string{
|
||||
token.ID.String(): "12345",
|
||||
token.Role.String(): "admin",
|
||||
token.Device.String(): "device-001",
|
||||
token.UID.String(): "user-999",
|
||||
token.Account.String(): "test@example.com",
|
||||
token.Scope.String(): "read:write",
|
||||
}
|
||||
|
||||
a := NewAdditional(validCases)
|
||||
|
||||
result := a.GetAll()
|
||||
assert.Equal(t, validCases, result)
|
||||
|
||||
}
|
|
@ -0,0 +1,527 @@
|
|||
package usecase
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"code.30cm.net/digimon/app-cloudep-permission-server/pkg/domain"
|
||||
"code.30cm.net/digimon/app-cloudep-permission-server/pkg/domain/entity"
|
||||
"code.30cm.net/digimon/app-cloudep-permission-server/pkg/domain/repository"
|
||||
dt "code.30cm.net/digimon/app-cloudep-permission-server/pkg/domain/token"
|
||||
"code.30cm.net/digimon/app-cloudep-permission-server/pkg/domain/usecase"
|
||||
ers "code.30cm.net/digimon/library-go/errs"
|
||||
"github.com/golang-jwt/jwt/v4"
|
||||
"github.com/segmentio/ksuid"
|
||||
"github.com/zeromicro/go-zero/core/logx"
|
||||
)
|
||||
|
||||
type TokenUseCaseParam struct {
|
||||
TokenRepo repository.TokenRepo
|
||||
RefreshExpires time.Duration
|
||||
Expired time.Duration
|
||||
Secret string
|
||||
}
|
||||
|
||||
type TokenUseCase struct {
|
||||
TokenUseCaseParam
|
||||
Token struct {
|
||||
RefreshExpires time.Duration
|
||||
Expired time.Duration
|
||||
Secret string
|
||||
}
|
||||
}
|
||||
|
||||
func NewTokenUseCase(param TokenUseCaseParam) usecase.TokenUseCase {
|
||||
return &TokenUseCase{
|
||||
TokenUseCaseParam: param,
|
||||
Token: struct {
|
||||
RefreshExpires time.Duration
|
||||
Expired time.Duration
|
||||
Secret string
|
||||
}{
|
||||
RefreshExpires: param.RefreshExpires,
|
||||
Expired: param.Expired,
|
||||
Secret: param.Secret,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (use *TokenUseCase) GenerateAccessToken(ctx context.Context, req usecase.GenerateTokenRequest) (usecase.AccessTokenResponse, error) {
|
||||
token, err := use.newToken(ctx, &req)
|
||||
if err != nil {
|
||||
return usecase.AccessTokenResponse{}, err
|
||||
}
|
||||
|
||||
err = use.TokenRepo.Create(ctx, *token)
|
||||
if err != nil {
|
||||
// 錯誤代碼
|
||||
e := domain.TokenErrorL(
|
||||
domain.TokenCreateErrorCode,
|
||||
logx.WithContext(ctx),
|
||||
[]logx.LogField{
|
||||
{Key: "req", Value: req},
|
||||
{Key: "func", Value: "TokenRepo.Create"},
|
||||
{Key: "err", Value: err.Error()},
|
||||
},
|
||||
"failed to create token").Wrap(err)
|
||||
|
||||
return usecase.AccessTokenResponse{}, e
|
||||
}
|
||||
|
||||
return usecase.AccessTokenResponse{
|
||||
AccessToken: token.AccessToken,
|
||||
ExpiresIn: token.ExpiresIn,
|
||||
RefreshToken: token.RefreshToken,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (use *TokenUseCase) RefreshAccessToken(ctx context.Context, req usecase.RefreshTokenRequest) (usecase.RefreshTokenResponse, error) {
|
||||
// Step 1: 檢查 refresh token
|
||||
token, err := use.TokenRepo.GetAccessTokenByOneTimeToken(ctx, req.Token)
|
||||
if err != nil {
|
||||
return usecase.RefreshTokenResponse{},
|
||||
use.wrapTokenError(ctx, wrapTokenErrorReq{
|
||||
funcName: "TokenRepo.GetAccessTokenByOneTimeToken",
|
||||
req: req,
|
||||
err: err,
|
||||
message: "failed to get access token",
|
||||
errorCode: domain.TokenRefreshErrorCode,
|
||||
})
|
||||
}
|
||||
|
||||
// Step 2: 提取 Claims Data
|
||||
claimsData, err := use.ParseSystemClaimsByAccessToken(token.AccessToken, use.Token.Secret, false)
|
||||
if err != nil {
|
||||
return usecase.RefreshTokenResponse{},
|
||||
use.wrapTokenError(ctx, wrapTokenErrorReq{
|
||||
funcName: "extractClaims",
|
||||
req: req,
|
||||
err: err,
|
||||
message: "failed to extract claims",
|
||||
errorCode: domain.TokenRefreshErrorCode,
|
||||
})
|
||||
}
|
||||
|
||||
data := NewAdditional(claimsData)
|
||||
data.Set(dt.Scope, req.Scope)
|
||||
data.Set(dt.Device, req.DeviceID)
|
||||
|
||||
// Step 3: 創建新 token
|
||||
newToken, err := use.newToken(ctx, &usecase.GenerateTokenRequest{
|
||||
Scope: req.Scope,
|
||||
DeviceID: req.DeviceID,
|
||||
Expires: req.Expires,
|
||||
RefreshExpires: req.RefreshExpires,
|
||||
Data: data.GetAll(),
|
||||
Role: data.Get(dt.Role),
|
||||
UID: data.Get(dt.UID),
|
||||
Account: data.Get(dt.Account),
|
||||
})
|
||||
if err != nil {
|
||||
return usecase.RefreshTokenResponse{},
|
||||
use.wrapTokenError(ctx, wrapTokenErrorReq{
|
||||
funcName: "use.newToken",
|
||||
req: req,
|
||||
err: err,
|
||||
message: "failed to create new token",
|
||||
errorCode: domain.TokenRefreshErrorCode,
|
||||
})
|
||||
}
|
||||
|
||||
if err := use.TokenRepo.Create(ctx, *newToken); err != nil {
|
||||
return usecase.RefreshTokenResponse{},
|
||||
use.wrapTokenError(ctx, wrapTokenErrorReq{
|
||||
funcName: "TokenRepo.Create",
|
||||
req: req,
|
||||
err: err,
|
||||
message: "failed to create new token",
|
||||
errorCode: domain.TokenRefreshErrorCode,
|
||||
})
|
||||
}
|
||||
|
||||
// Step 4: 刪除舊 token 並創建新 token
|
||||
if err := use.TokenRepo.Delete(ctx, token); err != nil {
|
||||
return usecase.RefreshTokenResponse{},
|
||||
use.wrapTokenError(ctx, wrapTokenErrorReq{
|
||||
funcName: "TokenRepo.Delete",
|
||||
req: req,
|
||||
err: err,
|
||||
message: "failed to delete old token",
|
||||
errorCode: domain.TokenRefreshErrorCode,
|
||||
})
|
||||
}
|
||||
|
||||
// 返回新的 Token 響應
|
||||
return usecase.RefreshTokenResponse{
|
||||
AccessToken: newToken.AccessToken,
|
||||
RefreshToken: newToken.RefreshToken,
|
||||
ExpiresIn: newToken.ExpiresIn,
|
||||
TokenType: data.Get(dt.Type),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (use *TokenUseCase) RevokeToken(ctx context.Context, req usecase.TokenRequest) error {
|
||||
claims, err := use.ParseSystemClaimsByAccessToken(req.Token, use.Token.Secret, false)
|
||||
if err != nil {
|
||||
return use.wrapTokenError(ctx, wrapTokenErrorReq{
|
||||
funcName: "CancelToken extractClaims",
|
||||
req: req,
|
||||
err: err,
|
||||
message: "failed to get token claims",
|
||||
errorCode: domain.TokenCancelErrorCode,
|
||||
})
|
||||
}
|
||||
|
||||
data := NewAdditional(claims)
|
||||
token, err := use.TokenRepo.GetAccessTokenByID(ctx, data.Get(dt.ID))
|
||||
if err != nil {
|
||||
return use.wrapTokenError(ctx, wrapTokenErrorReq{
|
||||
funcName: "TokenRepo GetAccessTokenByID",
|
||||
req: req,
|
||||
err: err,
|
||||
message: fmt.Sprintf("failed to get token claims :%s", data.Get(dt.ID)),
|
||||
errorCode: domain.TokenCancelErrorCode,
|
||||
})
|
||||
}
|
||||
|
||||
err = use.TokenRepo.Delete(ctx, token)
|
||||
if err != nil {
|
||||
return use.wrapTokenError(ctx, wrapTokenErrorReq{
|
||||
funcName: "TokenRepo Delete",
|
||||
req: req,
|
||||
err: err,
|
||||
message: fmt.Sprintf("failed to delete token :%s", token.ID),
|
||||
errorCode: domain.TokenCancelErrorCode,
|
||||
})
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (use *TokenUseCase) VerifyToken(ctx context.Context, req usecase.TokenRequest) (usecase.VerifyTokenResponse, error) {
|
||||
claims, err := use.ParseSystemClaimsByAccessToken(req.Token, use.Token.Secret, true)
|
||||
if err != nil {
|
||||
return usecase.VerifyTokenResponse{},
|
||||
use.wrapTokenError(ctx, wrapTokenErrorReq{
|
||||
funcName: "parseClaims",
|
||||
req: req,
|
||||
err: err,
|
||||
message: "validate token claims error",
|
||||
errorCode: domain.TokenValidateErrorCode,
|
||||
})
|
||||
}
|
||||
data := NewAdditional(claims)
|
||||
|
||||
token, err := use.TokenRepo.GetAccessTokenByID(ctx, data.Get(dt.ID))
|
||||
if err != nil {
|
||||
return usecase.VerifyTokenResponse{},
|
||||
use.wrapTokenError(ctx, wrapTokenErrorReq{
|
||||
funcName: "TokenRepo.GetAccessTokenByID",
|
||||
req: req,
|
||||
err: err,
|
||||
message: fmt.Sprintf("failed to get token :%s", data.Get(dt.ID)),
|
||||
errorCode: domain.TokenValidateErrorCode,
|
||||
})
|
||||
}
|
||||
|
||||
return usecase.VerifyTokenResponse{
|
||||
Token: token,
|
||||
Data: data.GetAll(),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (use *TokenUseCase) RevokeTokensByUID(ctx context.Context, req usecase.RevokeTokensByUIDRequest) error {
|
||||
if req.UID != "" {
|
||||
err := use.TokenRepo.DeleteAccessTokensByUID(ctx, req.UID)
|
||||
if err != nil {
|
||||
return use.wrapTokenError(ctx, wrapTokenErrorReq{
|
||||
funcName: "TokenRepo.DeleteAccessTokensByUID",
|
||||
req: req,
|
||||
err: err,
|
||||
message: "failed to cancel tokens by uid",
|
||||
errorCode: domain.TokensCancelErrorCode,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
if len(req.IDs) > 0 {
|
||||
err := use.TokenRepo.DeleteAccessTokenByID(ctx, req.IDs)
|
||||
if err != nil {
|
||||
return use.wrapTokenError(ctx, wrapTokenErrorReq{
|
||||
funcName: "TokenRepo.DeleteAccessTokenByID",
|
||||
req: req,
|
||||
err: err,
|
||||
message: "failed to cancel tokens by token ids",
|
||||
errorCode: domain.TokensCancelErrorCode,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (use *TokenUseCase) RevokeTokensByDeviceID(ctx context.Context, deviceID string) error {
|
||||
err := use.TokenRepo.DeleteAccessTokensByDeviceID(ctx, deviceID)
|
||||
if err != nil {
|
||||
return use.wrapTokenError(ctx, wrapTokenErrorReq{
|
||||
funcName: "TokenRepo.DeleteAccessTokensByDeviceID",
|
||||
req: deviceID,
|
||||
err: err,
|
||||
message: "failed to cancel token by device id",
|
||||
errorCode: domain.TokensCancelErrorCode,
|
||||
})
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (use *TokenUseCase) GetUserTokensByDeviceID(ctx context.Context, deviceID string) ([]*usecase.AccessTokenResponse, error) {
|
||||
tokens, err := use.TokenRepo.GetAccessTokensByDeviceID(ctx, deviceID)
|
||||
if err != nil {
|
||||
return nil, use.wrapTokenError(ctx, wrapTokenErrorReq{
|
||||
funcName: "TokenRepo.GetAccessTokensByDeviceID",
|
||||
req: deviceID,
|
||||
err: err,
|
||||
message: "failed to get token by device id",
|
||||
errorCode: domain.TokenGetErrorCode,
|
||||
})
|
||||
}
|
||||
|
||||
result := make([]*usecase.AccessTokenResponse, 0, len(tokens))
|
||||
for _, v := range tokens {
|
||||
result = append(result, &usecase.AccessTokenResponse{
|
||||
AccessToken: v.AccessToken,
|
||||
ExpiresIn: v.ExpiresIn,
|
||||
RefreshToken: v.RefreshToken,
|
||||
})
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (use *TokenUseCase) GetUserTokensByUID(ctx context.Context, uid string) ([]*usecase.AccessTokenResponse, error) {
|
||||
tokens, err := use.TokenRepo.GetAccessTokensByUID(ctx, uid)
|
||||
if err != nil {
|
||||
return nil, use.wrapTokenError(ctx, wrapTokenErrorReq{
|
||||
funcName: "TokenRepo.GetAccessTokensByUID",
|
||||
req: uid,
|
||||
err: err,
|
||||
message: "failed to get token by uid",
|
||||
errorCode: domain.TokenGetErrorCode,
|
||||
})
|
||||
}
|
||||
|
||||
result := make([]*usecase.AccessTokenResponse, 0, len(tokens))
|
||||
for _, v := range tokens {
|
||||
result = append(result, &usecase.AccessTokenResponse{
|
||||
AccessToken: v.AccessToken,
|
||||
ExpiresIn: v.ExpiresIn,
|
||||
RefreshToken: v.RefreshToken,
|
||||
})
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (use *TokenUseCase) ReadTokenBasicData(ctx context.Context, token string) (usecase.Additional, error) {
|
||||
claims, err := use.ParseSystemClaimsByAccessToken(token, use.Token.Secret, false)
|
||||
if err != nil {
|
||||
return nil,
|
||||
use.wrapTokenError(ctx, wrapTokenErrorReq{
|
||||
funcName: "parseClaims",
|
||||
req: token,
|
||||
err: err,
|
||||
message: "validate token claims error",
|
||||
errorCode: domain.TokenValidateErrorCode,
|
||||
})
|
||||
}
|
||||
|
||||
return NewAdditional(claims), nil
|
||||
}
|
||||
|
||||
// ======== JWT Token ========
|
||||
|
||||
// CreateAccessToken 會將基本 token 以及想要加入Token Claims 的Data 依照 secret key 加密之後變成 jwt access token
|
||||
func (use *TokenUseCase) CreateAccessToken(token entity.Token, data any, secretKey string) (string, error) {
|
||||
claims := entity.Claims{
|
||||
Data: data,
|
||||
RegisteredClaims: jwt.RegisteredClaims{
|
||||
ID: token.ID,
|
||||
ExpiresAt: jwt.NewNumericDate(time.Unix(0, token.ExpiresIn)),
|
||||
Issuer: dt.Issuer,
|
||||
},
|
||||
}
|
||||
|
||||
accessToken, err := jwt.NewWithClaims(jwt.SigningMethodHS256, claims).
|
||||
SignedString([]byte(secretKey))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return accessToken, nil
|
||||
}
|
||||
|
||||
func (use *TokenUseCase) CreateRefreshToken(accessToken string) string {
|
||||
hash := sha256.New()
|
||||
_, _ = hash.Write([]byte(accessToken))
|
||||
|
||||
return hex.EncodeToString(hash.Sum(nil))
|
||||
}
|
||||
|
||||
func (use *TokenUseCase) ParseJWTClaimsByAccessToken(accessToken string, secret string, validate bool) (jwt.MapClaims, error) {
|
||||
// 跳過驗證的解析
|
||||
var token *jwt.Token
|
||||
var err error
|
||||
|
||||
if validate {
|
||||
token, err = jwt.Parse(accessToken, func(token *jwt.Token) (any, error) {
|
||||
if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
|
||||
return nil, fmt.Errorf("token unexpected signing method: %v", token.Header["alg"])
|
||||
}
|
||||
|
||||
return []byte(secret), nil
|
||||
})
|
||||
if err != nil {
|
||||
return jwt.MapClaims{}, err
|
||||
}
|
||||
} else {
|
||||
parser := jwt.NewParser(jwt.WithoutClaimsValidation())
|
||||
token, err = parser.Parse(accessToken, func(_ *jwt.Token) (any, error) {
|
||||
return []byte(secret), nil
|
||||
})
|
||||
if err != nil {
|
||||
return jwt.MapClaims{}, err
|
||||
}
|
||||
}
|
||||
|
||||
claims, ok := token.Claims.(jwt.MapClaims)
|
||||
if !ok && token.Valid {
|
||||
return jwt.MapClaims{}, fmt.Errorf("token valid error")
|
||||
}
|
||||
|
||||
return claims, nil
|
||||
}
|
||||
|
||||
func (use *TokenUseCase) ParseSystemClaimsByAccessToken(accessToken string, secret string, validate bool) (map[string]string, error) {
|
||||
claimMap, err := use.ParseJWTClaimsByAccessToken(accessToken, secret, validate)
|
||||
if err != nil {
|
||||
return map[string]string{}, err
|
||||
}
|
||||
|
||||
claimsData, ok := claimMap["data"].(map[string]any)
|
||||
if ok {
|
||||
return convertMap(claimsData), nil
|
||||
}
|
||||
|
||||
return map[string]string{}, fmt.Errorf("get data from claim map error")
|
||||
}
|
||||
|
||||
// ======== 工具 ========
|
||||
|
||||
func (use *TokenUseCase) newToken(ctx context.Context, req *usecase.GenerateTokenRequest) (*entity.Token, error) {
|
||||
// 準備建立 Token 所需
|
||||
now := time.Now().UTC()
|
||||
expires := req.Expires
|
||||
refreshExpires := req.RefreshExpires
|
||||
|
||||
if expires <= 0 {
|
||||
// 將時間加上 n 秒 -> 系統內預設
|
||||
sec := time.Duration(use.Token.Expired.Seconds()) * time.Second
|
||||
// 獲取 Unix 時間戳
|
||||
expires = now.Add(sec).UnixNano()
|
||||
}
|
||||
|
||||
// Refresh Token 過期時間要比普通的Token 長
|
||||
if req.RefreshExpires <= 0 {
|
||||
// 獲取 Unix 時間戳
|
||||
refresh := time.Duration(use.Token.RefreshExpires.Seconds()) * time.Second
|
||||
refreshExpires = now.Add(refresh).UnixNano()
|
||||
}
|
||||
|
||||
token := entity.Token{
|
||||
ID: ksuid.New().String(),
|
||||
DeviceID: req.DeviceID,
|
||||
ExpiresIn: expires,
|
||||
RefreshExpiresIn: refreshExpires,
|
||||
AccessCreateAt: now.UnixNano(),
|
||||
RefreshCreateAt: now.UnixNano(),
|
||||
UID: req.UID,
|
||||
}
|
||||
// 故意 data 裡面不會有那些已經有的欄位資訊
|
||||
data := NewAdditional(req.Data)
|
||||
data.Set(dt.ID, token.ID)
|
||||
data.Set(dt.Role, req.Role)
|
||||
data.Set(dt.Scope, req.Scope)
|
||||
data.Set(dt.Account, req.Account)
|
||||
data.Set(dt.UID, req.UID)
|
||||
data.Set(dt.Type, req.TokenType)
|
||||
|
||||
if req.DeviceID != "" {
|
||||
data.Set(dt.Device, req.DeviceID)
|
||||
}
|
||||
|
||||
var err error
|
||||
token.AccessToken, err = use.CreateAccessToken(token, data.GetAll(), use.Token.Secret)
|
||||
token.RefreshToken = use.CreateRefreshToken(token.AccessToken)
|
||||
|
||||
if err != nil {
|
||||
// 錯誤代碼 20-201-02
|
||||
e := domain.TokenErrorL(
|
||||
domain.TokenClaimErrorCode,
|
||||
logx.WithContext(ctx),
|
||||
[]logx.LogField{
|
||||
{Key: "req", Value: req},
|
||||
{Key: "func", Value: "accessTokenGenerator"},
|
||||
{Key: "err", Value: err.Error()},
|
||||
},
|
||||
"failed to generator access token").Wrap(err)
|
||||
|
||||
return nil, e
|
||||
}
|
||||
|
||||
return &token, nil
|
||||
}
|
||||
|
||||
func convertMap(input map[string]any) 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
|
||||
}
|
||||
|
||||
type wrapTokenErrorReq struct {
|
||||
funcName string
|
||||
req any
|
||||
err error
|
||||
message string
|
||||
errorCode ers.ErrorCode
|
||||
}
|
||||
|
||||
// wrapTokenError 將錯誤訊息封裝到 domain.TokenErrorL 中
|
||||
func (use *TokenUseCase) wrapTokenError(ctx context.Context, param wrapTokenErrorReq) error {
|
||||
logFields := []logx.LogField{
|
||||
{Key: "req", Value: param.req},
|
||||
{Key: "func", Value: param.funcName},
|
||||
{Key: "err", Value: param.err.Error()},
|
||||
}
|
||||
wrappedErr := domain.TokenErrorL(
|
||||
param.errorCode,
|
||||
logx.WithContext(ctx),
|
||||
logFields,
|
||||
param.message,
|
||||
).Wrap(param.err)
|
||||
|
||||
return wrappedErr
|
||||
}
|
|
@ -0,0 +1,611 @@
|
|||
package usecase
|
||||
|
||||
import (
|
||||
"code.30cm.net/digimon/app-cloudep-permission-server/pkg/domain/entity"
|
||||
"code.30cm.net/digimon/app-cloudep-permission-server/pkg/domain/usecase"
|
||||
mock "code.30cm.net/digimon/app-cloudep-permission-server/pkg/mock/repository"
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/golang-jwt/jwt/v4"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"go.uber.org/mock/gomock"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
// TestTokenUseCase_CreateAccessToken_TableDriven 透過 table-driven 方式測試 CreateAccessToken
|
||||
func TestTokenUseCase_CreateAccessToken_TableDriven(t *testing.T) {
|
||||
// 固定發行者設定,測試中會用來驗證 claims.Issuer
|
||||
now := time.Now()
|
||||
mockCtrl := gomock.NewController(t)
|
||||
defer mockCtrl.Finish()
|
||||
|
||||
mockAutoIDModel := mock.NewMockTokenRepo(mockCtrl)
|
||||
uc := NewTokenUseCase(TokenUseCaseParam{
|
||||
TokenRepo: mockAutoIDModel,
|
||||
RefreshExpires: 2 * time.Minute,
|
||||
Expired: 2 * time.Minute,
|
||||
Secret: "gg88g88",
|
||||
})
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
token entity.Token
|
||||
data any
|
||||
secretKey string
|
||||
wantErr bool
|
||||
verifyClaims func(t *testing.T, claims *entity.Claims, expectedExpiry time.Time)
|
||||
}{
|
||||
{
|
||||
name: "ok",
|
||||
token: entity.Token{
|
||||
ID: "token1",
|
||||
ExpiresIn: now.Add(1 * time.Hour).UnixNano(),
|
||||
},
|
||||
data: map[string]interface{}{"foo": "bar"},
|
||||
secretKey: "secret123",
|
||||
wantErr: false,
|
||||
verifyClaims: func(t *testing.T, claims *entity.Claims, expectedExpiry time.Time) {
|
||||
assert.Equal(t, "token1", claims.ID)
|
||||
|
||||
assert.True(t, claims.ExpiresAt.Time.Before(expectedExpiry),
|
||||
"expected expiry %v, got %v", expectedExpiry, claims.ExpiresAt.Time)
|
||||
|
||||
dataMap, ok := claims.Data.(map[string]interface{})
|
||||
assert.True(t, ok, "claims.Data 應為 map[string]interface{}")
|
||||
assert.Equal(t, "bar", dataMap["foo"])
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "valid token with string data",
|
||||
token: entity.Token{
|
||||
ID: "token2",
|
||||
ExpiresIn: now.Add(2 * time.Hour).UnixNano(),
|
||||
},
|
||||
data: map[string]interface{}{"foo": "bar"},
|
||||
secretKey: "anotherSecret",
|
||||
wantErr: false,
|
||||
verifyClaims: func(t *testing.T, claims *entity.Claims, expectedExpiry time.Time) {
|
||||
assert.Equal(t, "token2", claims.ID)
|
||||
assert.True(t, claims.ExpiresAt.Time.Before(expectedExpiry))
|
||||
assert.Equal(t, map[string]interface{}{"foo": "bar"}, claims.Data)
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "empty secret key",
|
||||
token: entity.Token{
|
||||
ID: "token3",
|
||||
ExpiresIn: now.Add(30 * time.Minute).UnixNano(),
|
||||
},
|
||||
data: map[string]interface{}{"key": "value"},
|
||||
secretKey: "",
|
||||
wantErr: false,
|
||||
verifyClaims: func(t *testing.T, claims *entity.Claims, expectedExpiry time.Time) {
|
||||
assert.Equal(t, "token3", claims.ID)
|
||||
assert.True(t, claims.ExpiresAt.Time.Before(expectedExpiry))
|
||||
dataMap, ok := claims.Data.(map[string]interface{})
|
||||
assert.True(t, ok, "claims.Data 應為 map[string]interface{}")
|
||||
assert.Equal(t, "value", dataMap["key"])
|
||||
},
|
||||
},
|
||||
// 如有需要,可加入更多測試案例,例如模擬簽名錯誤等情境
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
tt := tt // 捕捉範圍變數
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
|
||||
jwtStr, err := uc.CreateAccessToken(tt.token, tt.data, tt.secretKey)
|
||||
if tt.wantErr {
|
||||
assert.Error(t, err)
|
||||
return
|
||||
}
|
||||
assert.NoError(t, err)
|
||||
|
||||
// 解析 JWT
|
||||
parsedToken, err := jwt.ParseWithClaims(jwtStr, &entity.Claims{}, func(token *jwt.Token) (interface{}, error) {
|
||||
// 驗證簽名方法是否為 HMAC
|
||||
if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
|
||||
return nil, fmt.Errorf("unexpected signing method: %v", token.Header["alg"])
|
||||
}
|
||||
return []byte(tt.secretKey), nil
|
||||
})
|
||||
assert.NoError(t, err)
|
||||
assert.True(t, parsedToken.Valid, "解析後的 JWT 應該有效")
|
||||
|
||||
claims, ok := parsedToken.Claims.(*entity.Claims)
|
||||
assert.True(t, ok, "claims 型別錯誤,預期 *entity.Claims, got %T", parsedToken.Claims)
|
||||
|
||||
// 根據 token.ExpiresIn 計算預期的過期時間
|
||||
expectedExpiry := time.Unix(0, tt.token.ExpiresIn)
|
||||
// 呼叫 verifyClaims 驗證其它 Claim 資料
|
||||
tt.verifyClaims(t, claims, expectedExpiry)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestTokenUseCase_CreateRefreshToken(t *testing.T) {
|
||||
mockCtrl := gomock.NewController(t)
|
||||
defer mockCtrl.Finish()
|
||||
|
||||
mockAutoIDModel := mock.NewMockTokenRepo(mockCtrl)
|
||||
uc := NewTokenUseCase(TokenUseCaseParam{
|
||||
TokenRepo: mockAutoIDModel,
|
||||
RefreshExpires: 2 * time.Minute,
|
||||
Expired: 2 * time.Minute,
|
||||
Secret: "gg88g88",
|
||||
})
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
accessToken string
|
||||
expected string
|
||||
}{
|
||||
{
|
||||
name: "empty access token",
|
||||
accessToken: "",
|
||||
// SHA256("") 的 hex 編碼結果
|
||||
expected: "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
|
||||
},
|
||||
{
|
||||
name: "normal access token",
|
||||
accessToken: "access-token",
|
||||
expected: "3f16bed7089f4653e5ef21bfd2824d7f3aaaecc7a598e7e89c580e1606a9cc52",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
tt := tt // 捕捉變數
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
result := uc.CreateRefreshToken(tt.accessToken)
|
||||
assert.Equal(t, tt.expected, result)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestTokenUseCase_ParseJWTClaimsByAccessToken 使用 table-driven 方式測試 ParseJWTClaimsByAccessToken
|
||||
func TestTokenUseCase_ParseJWTClaimsByAccessToken(t *testing.T) {
|
||||
mockCtrl := gomock.NewController(t)
|
||||
defer mockCtrl.Finish()
|
||||
|
||||
mockAutoIDModel := mock.NewMockTokenRepo(mockCtrl)
|
||||
uc := NewTokenUseCase(TokenUseCaseParam{
|
||||
TokenRepo: mockAutoIDModel,
|
||||
RefreshExpires: 2 * time.Minute,
|
||||
Expired: 2 * time.Minute,
|
||||
Secret: "gg88g88",
|
||||
})
|
||||
|
||||
// 定義測試案例的結構
|
||||
tests := []struct {
|
||||
name string
|
||||
// tokenGen 用來動態產生要解析的 access token
|
||||
tokenGen func(t *testing.T) string
|
||||
secret string
|
||||
validate bool
|
||||
wantClaims jwt.MapClaims
|
||||
wantErr bool
|
||||
errContains string
|
||||
}{
|
||||
{
|
||||
name: "valid token with validation",
|
||||
tokenGen: func(t *testing.T) string {
|
||||
claims := jwt.MapClaims{
|
||||
"sub": "123",
|
||||
"role": "admin",
|
||||
}
|
||||
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
|
||||
tokenString, err := token.SignedString([]byte("testsecret"))
|
||||
if err != nil {
|
||||
t.Fatalf("failed to sign token: %v", err)
|
||||
}
|
||||
return tokenString
|
||||
},
|
||||
secret: "testsecret",
|
||||
validate: true,
|
||||
wantClaims: jwt.MapClaims{
|
||||
"sub": "123",
|
||||
"role": "admin",
|
||||
},
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "valid token without validation",
|
||||
tokenGen: func(t *testing.T) string {
|
||||
claims := jwt.MapClaims{
|
||||
"sub": "123",
|
||||
"role": "admin",
|
||||
}
|
||||
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
|
||||
tokenString, err := token.SignedString([]byte("testsecret"))
|
||||
if err != nil {
|
||||
t.Fatalf("failed to sign token: %v", err)
|
||||
}
|
||||
return tokenString
|
||||
},
|
||||
secret: "testsecret",
|
||||
validate: false,
|
||||
wantClaims: jwt.MapClaims{
|
||||
"sub": "123",
|
||||
"role": "admin",
|
||||
},
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "invalid secret",
|
||||
tokenGen: func(t *testing.T) string {
|
||||
claims := jwt.MapClaims{
|
||||
"sub": "123",
|
||||
}
|
||||
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
|
||||
tokenString, err := token.SignedString([]byte("testsecret"))
|
||||
if err != nil {
|
||||
t.Fatalf("failed to sign token: %v", err)
|
||||
}
|
||||
return tokenString
|
||||
},
|
||||
secret: "wrongsecret",
|
||||
validate: true,
|
||||
wantErr: true,
|
||||
errContains: "signature", // 預期錯誤訊息中包含 "signature"
|
||||
},
|
||||
{
|
||||
name: "unexpected signing method",
|
||||
tokenGen: func(t *testing.T) string {
|
||||
claims := jwt.MapClaims{
|
||||
"sub": "456",
|
||||
}
|
||||
// 使用 SigningMethodNone 產生 token
|
||||
token := jwt.NewWithClaims(jwt.SigningMethodNone, claims)
|
||||
// 針對 None 演算法,SignedString 需要使用 jwt.UnsafeAllowNoneSignatureType
|
||||
tokenString, err := token.SignedString(jwt.UnsafeAllowNoneSignatureType)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to sign token: %v", err)
|
||||
}
|
||||
return tokenString
|
||||
},
|
||||
secret: "testsecret",
|
||||
validate: true,
|
||||
wantErr: true,
|
||||
errContains: "unexpected signing method",
|
||||
},
|
||||
{
|
||||
name: "malformed token",
|
||||
tokenGen: func(t *testing.T) string {
|
||||
return "not-a-token"
|
||||
},
|
||||
secret: "testsecret",
|
||||
validate: true,
|
||||
wantErr: true,
|
||||
errContains: "token contains an invalid number of segments",
|
||||
},
|
||||
}
|
||||
|
||||
// 針對每個測試案例執行測試
|
||||
for _, tt := range tests {
|
||||
tt := tt // 捕捉迴圈變數
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
// 產生 access token
|
||||
accessToken := tt.tokenGen(t)
|
||||
|
||||
claims, err := uc.ParseJWTClaimsByAccessToken(accessToken, tt.secret, tt.validate)
|
||||
if tt.wantErr {
|
||||
assert.Error(t, err)
|
||||
if err != nil && tt.errContains != "" {
|
||||
assert.Contains(t, err.Error(), tt.errContains)
|
||||
}
|
||||
return
|
||||
}
|
||||
assert.NoError(t, err)
|
||||
// 驗證解析出來的 claims 是否符合預期
|
||||
assert.Equal(t, tt.wantClaims, claims)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestTokenUseCase_ParseSystemClaimsByAccessToken(t *testing.T) {
|
||||
mockCtrl := gomock.NewController(t)
|
||||
defer mockCtrl.Finish()
|
||||
|
||||
mockAutoIDModel := mock.NewMockTokenRepo(mockCtrl)
|
||||
uc := NewTokenUseCase(TokenUseCaseParam{
|
||||
TokenRepo: mockAutoIDModel,
|
||||
RefreshExpires: 2 * time.Minute,
|
||||
Expired: 2 * time.Minute,
|
||||
Secret: "gg88g88",
|
||||
})
|
||||
//table-driven 測試案例
|
||||
tests := []struct {
|
||||
name string
|
||||
tokenGen func(t *testing.T) string // 用來產生 access token
|
||||
secret string
|
||||
validate bool
|
||||
want map[string]string // 預期轉換後的資料
|
||||
wantErr bool
|
||||
errContains string
|
||||
}{
|
||||
{
|
||||
name: "valid token with correct data map",
|
||||
tokenGen: func(t *testing.T) string {
|
||||
// 建立 claims,其中 "data" 欄位為 map[string]any
|
||||
claims := jwt.MapClaims{
|
||||
"data": map[string]any{
|
||||
"key1": "value1",
|
||||
"key2": "value2",
|
||||
},
|
||||
}
|
||||
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
|
||||
tokenStr, err := token.SignedString([]byte("secret"))
|
||||
if err != nil {
|
||||
t.Fatalf("failed to sign token: %v", err)
|
||||
}
|
||||
return tokenStr
|
||||
},
|
||||
secret: "secret",
|
||||
validate: true,
|
||||
want: map[string]string{
|
||||
"key1": "value1",
|
||||
"key2": "value2",
|
||||
},
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "token missing data field",
|
||||
tokenGen: func(t *testing.T) string {
|
||||
// claims 中不包含 "data"
|
||||
claims := jwt.MapClaims{
|
||||
"other": "something",
|
||||
}
|
||||
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
|
||||
tokenStr, err := token.SignedString([]byte("secret"))
|
||||
if err != nil {
|
||||
t.Fatalf("failed to sign token: %v", err)
|
||||
}
|
||||
return tokenStr
|
||||
},
|
||||
secret: "secret",
|
||||
validate: true,
|
||||
want: map[string]string{},
|
||||
wantErr: true,
|
||||
errContains: "get data from claim map error",
|
||||
},
|
||||
{
|
||||
name: "malformed token",
|
||||
tokenGen: func(t *testing.T) string {
|
||||
return "not-a-token"
|
||||
},
|
||||
secret: "secret",
|
||||
validate: true,
|
||||
want: map[string]string{},
|
||||
wantErr: true,
|
||||
errContains: "token contains an invalid number of segments",
|
||||
},
|
||||
{
|
||||
name: "data field not a map",
|
||||
tokenGen: func(t *testing.T) string {
|
||||
// 將 "data" 設為一個字串,而非 map
|
||||
claims := jwt.MapClaims{
|
||||
"data": "not-a-map",
|
||||
}
|
||||
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
|
||||
tokenStr, err := token.SignedString([]byte("secret"))
|
||||
if err != nil {
|
||||
t.Fatalf("failed to sign token: %v", err)
|
||||
}
|
||||
return tokenStr
|
||||
},
|
||||
secret: "secret",
|
||||
validate: true,
|
||||
want: map[string]string{},
|
||||
wantErr: true,
|
||||
errContains: "get data from claim map error",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
tt := tt // 捕捉區域變數
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
|
||||
accessToken := tt.tokenGen(t)
|
||||
result, err := uc.ParseSystemClaimsByAccessToken(accessToken, tt.secret, tt.validate)
|
||||
if tt.wantErr {
|
||||
assert.Error(t, err)
|
||||
if err != nil && tt.errContains != "" {
|
||||
assert.Contains(t, err.Error(), tt.errContains)
|
||||
}
|
||||
return
|
||||
}
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, tt.want, result)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestTokenUseCase_newToken(t *testing.T) {
|
||||
mockCtrl := gomock.NewController(t)
|
||||
defer mockCtrl.Finish()
|
||||
|
||||
mockAutoIDModel := mock.NewMockTokenRepo(mockCtrl)
|
||||
uc := TokenUseCase{
|
||||
TokenUseCaseParam: TokenUseCaseParam{
|
||||
TokenRepo: mockAutoIDModel,
|
||||
RefreshExpires: 2 * time.Minute,
|
||||
Expired: 2 * time.Minute,
|
||||
Secret: "gg88g88",
|
||||
},
|
||||
}
|
||||
|
||||
// 取得一個參考時間,用來檢查 default expiration 的結果
|
||||
nowRef := time.Now().UTC()
|
||||
tests := []struct {
|
||||
name string
|
||||
req *usecase.GenerateTokenRequest
|
||||
// 模擬產生 AccessToken 與 RefreshToken 的函式
|
||||
stubAccessToken func(token entity.Token, data map[string]interface{}, secret string) (string, error)
|
||||
stubRefreshToken func(accessToken string) string
|
||||
wantErr bool
|
||||
// 當使用者提供明確的 expires 與 refreshExpires 時,期望的值(否則使用預設)
|
||||
expectExpiresProvided bool
|
||||
expectedExpires int64
|
||||
expectRefreshExpiresProvided bool
|
||||
expectedRefreshExpires int64
|
||||
}{
|
||||
{
|
||||
name: "default expiration used when req.Expires/RefreshExpires are zero",
|
||||
req: &usecase.GenerateTokenRequest{
|
||||
DeviceID: "device1",
|
||||
UID: "user1",
|
||||
Expires: 0,
|
||||
RefreshExpires: 0,
|
||||
Data: map[string]string{"foo": "bar"},
|
||||
Role: "admin",
|
||||
Scope: "read",
|
||||
Account: "account1",
|
||||
TokenType: "access",
|
||||
},
|
||||
wantErr: false,
|
||||
expectExpiresProvided: false,
|
||||
expectRefreshExpiresProvided: false,
|
||||
},
|
||||
{
|
||||
name: "explicit expiration provided",
|
||||
req: func() *usecase.GenerateTokenRequest {
|
||||
// 提供明確的 expires 與 refreshExpires
|
||||
exp := nowRef.Add(5 * time.Minute).UnixNano()
|
||||
refExp := nowRef.Add(10 * time.Minute).UnixNano()
|
||||
return &usecase.GenerateTokenRequest{
|
||||
DeviceID: "device2",
|
||||
UID: "user2",
|
||||
Expires: exp,
|
||||
RefreshExpires: refExp,
|
||||
Data: map[string]string{},
|
||||
Role: "user",
|
||||
Scope: "write",
|
||||
Account: "account2",
|
||||
TokenType: "access",
|
||||
}
|
||||
}(),
|
||||
stubAccessToken: func(token entity.Token, data map[string]interface{}, secret string) (string, error) {
|
||||
return "access-token", nil
|
||||
},
|
||||
stubRefreshToken: func(accessToken string) string {
|
||||
return "refresh-token"
|
||||
},
|
||||
wantErr: false,
|
||||
expectExpiresProvided: true,
|
||||
// 預期值就與 req.Expires 相同
|
||||
expectedExpires: func() int64 { return nowRef.Add(5 * time.Minute).UnixNano() }(),
|
||||
expectRefreshExpiresProvided: true,
|
||||
expectedRefreshExpires: func() int64 { return nowRef.Add(10 * time.Minute).UnixNano() }(),
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
tt := tt // 捕捉範圍變數
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
// 呼叫 newToken 方法
|
||||
token, err := uc.newToken(context.Background(), tt.req)
|
||||
if tt.wantErr {
|
||||
assert.Error(t, err)
|
||||
return
|
||||
}
|
||||
assert.NoError(t, err)
|
||||
// 檢查基本欄位
|
||||
assert.NotEmpty(t, token.ID, "token.ID should not be empty")
|
||||
assert.Equal(t, tt.req.DeviceID, token.DeviceID)
|
||||
assert.Equal(t, tt.req.UID, token.UID)
|
||||
|
||||
// 驗證建立時間欄位有被設置
|
||||
assert.NotZero(t, token.AccessCreateAt)
|
||||
assert.NotZero(t, token.RefreshCreateAt)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestTokenUseCase_GenerateAccessToken(t *testing.T) {
|
||||
mockCtrl := gomock.NewController(t)
|
||||
defer mockCtrl.Finish()
|
||||
|
||||
mockNewMockTokenRepo := mock.NewMockTokenRepo(mockCtrl)
|
||||
uc := TokenUseCase{
|
||||
TokenUseCaseParam: TokenUseCaseParam{
|
||||
TokenRepo: mockNewMockTokenRepo,
|
||||
RefreshExpires: 2 * time.Minute,
|
||||
Expired: 2 * time.Minute,
|
||||
Secret: "gg88g88",
|
||||
},
|
||||
}
|
||||
// 定義 table-driven 測試案例
|
||||
tests := []struct {
|
||||
name string
|
||||
repoErr error
|
||||
req usecase.GenerateTokenRequest
|
||||
wantErr bool
|
||||
errContains string
|
||||
setup func()
|
||||
// 若成功,預期回傳的 access token 與 refresh token
|
||||
expectedAccessToken string
|
||||
expectedRefreshToken string
|
||||
}{
|
||||
{
|
||||
name: "newToken error from CreateAccessToken",
|
||||
repoErr: nil,
|
||||
setup: func() {
|
||||
mockNewMockTokenRepo.EXPECT().Create(gomock.Any(), gomock.Any()).Return(fmt.Errorf("token create error: failed to create token"))
|
||||
},
|
||||
req: usecase.GenerateTokenRequest{
|
||||
DeviceID: "device1",
|
||||
UID: "user1",
|
||||
Expires: 0, // 使用預設過期時間
|
||||
RefreshExpires: 0,
|
||||
Data: map[string]string{"foo": "bar"},
|
||||
Role: "admin",
|
||||
Scope: "read",
|
||||
Account: "account1",
|
||||
TokenType: "access",
|
||||
},
|
||||
wantErr: true,
|
||||
errContains: "token create error: failed to create token",
|
||||
},
|
||||
{
|
||||
name: "successful generation",
|
||||
repoErr: nil,
|
||||
setup: func() {
|
||||
mockNewMockTokenRepo.EXPECT().Create(gomock.Any(), gomock.Any()).Return(nil)
|
||||
},
|
||||
req: usecase.GenerateTokenRequest{
|
||||
DeviceID: "device3",
|
||||
UID: "user3",
|
||||
Expires: 0,
|
||||
RefreshExpires: 0,
|
||||
Data: map[string]string{"foo": "bar"},
|
||||
Role: "member",
|
||||
Scope: "read",
|
||||
Account: "account3",
|
||||
TokenType: "access",
|
||||
},
|
||||
wantErr: false,
|
||||
},
|
||||
}
|
||||
|
||||
// 針對每個測試案例執行測試
|
||||
for _, tt := range tests {
|
||||
tt := tt // 捕捉區域變數
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
tt.setup()
|
||||
resp, err := uc.GenerateAccessToken(ctx, tt.req)
|
||||
if tt.wantErr {
|
||||
assert.Error(t, err)
|
||||
if err != nil && tt.errContains != "" {
|
||||
assert.Contains(t, err.Error(), tt.errContains)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
assert.NoError(t, err)
|
||||
// 驗證 ExpiresIn 非零(newToken 會根據當前時間與設定產生過期時間)
|
||||
assert.NotZero(t, resp.ExpiresIn)
|
||||
})
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue