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")
|
GOFILES := $(shell find . -name "*.go")
|
||||||
LDFLAGS := -s -w
|
LDFLAGS := -s -w
|
||||||
VERSION="v1.0.1"
|
VERSION="v1.0.1"
|
||||||
DOCKER_REPO="igs170911/permission"
|
DOCKER_REPO="code.30cm.net/permission"
|
||||||
|
|
||||||
.PHONY: test
|
.PHONY: test
|
||||||
test: # 進行測試
|
test: # 進行測試
|
||||||
|
@ -46,24 +46,10 @@ build-docker:
|
||||||
rm -rf Dockerfile
|
rm -rf Dockerfile
|
||||||
@echo "Generate core-api files successfully"
|
@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
|
.PHONY: mock-gen
|
||||||
mock-gen: # 建立 mock 資料
|
mock-gen: # 建立 mock 資料
|
||||||
mockgen -source=./internal/model/permission_model.go -destination=./internal/mock/model/permission_model.go -package=mock
|
mockgen -source=./pkg/domain/repository/token.go -destination=./pkg/mock/repository/token.go -package=mock
|
||||||
mockgen -source=./internal/model/permission_model_gen.go -destination=./internal/mock/model/permission_model_gen.go -package=mock
|
@echo "Generate mock files successfully"
|
||||||
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
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
# BUILDER #
|
# BUILDER #
|
||||||
###########
|
###########
|
||||||
|
|
||||||
FROM golang:1.23.4 AS builder
|
FROM golang:1.24.0 AS builder
|
||||||
|
|
||||||
ARG VERSION
|
ARG VERSION
|
||||||
ARG BUILT
|
ARG BUILT
|
||||||
|
|
|
@ -2,7 +2,7 @@ syntax = "proto3";
|
||||||
|
|
||||||
package permission;
|
package permission;
|
||||||
|
|
||||||
option go_package="./app-cloudep-permission-server";
|
option go_package="./permission";
|
||||||
|
|
||||||
// OKResp
|
// OKResp
|
||||||
message OKResp {}
|
message OKResp {}
|
||||||
|
|
7
go.mod
7
go.mod
|
@ -1,11 +1,16 @@
|
||||||
module code.30cm.net/digimon/app-cloudep-permission-server
|
module code.30cm.net/digimon/app-cloudep-permission-server
|
||||||
|
|
||||||
go 1.23.4
|
go 1.23.6
|
||||||
|
|
||||||
require (
|
require (
|
||||||
|
code.30cm.net/digimon/library-go/errs v1.2.14
|
||||||
github.com/alicebob/miniredis/v2 v2.34.0
|
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/stretchr/testify v1.10.0
|
||||||
github.com/zeromicro/go-zero v1.8.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/grpc v1.70.0
|
||||||
google.golang.org/protobuf v1.36.5
|
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 h1:uvdUDbHQHO85qeSydJtItA4T55Pw6BtAejd0APRJOCE=
|
||||||
github.com/alicebob/gopher-json v0.0.0-20230218143504-906a9b012302/go.mod h1:SGnFV6hVsYE877CKEZ6tDNTjaSXYUk6QqoIK6PrAtcc=
|
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=
|
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/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 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
|
||||||
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
|
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 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc=
|
||||||
github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs=
|
github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs=
|
||||||
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
|
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/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 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ=
|
||||||
github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog=
|
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 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0bLI=
|
||||||
github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
|
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=
|
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/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 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
|
||||||
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
|
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 h1:7fIwc/ZtS0q++VgcfqFDxSBZVv/Xo49/SYnDFupUwlI=
|
||||||
go.uber.org/multierr v1.9.0/go.mod h1:X2jQV1h+kxSjClGpnseKVIxpmcjrj7MNnI0bnlfKTVQ=
|
go.uber.org/multierr v1.9.0/go.mod h1:X2jQV1h+kxSjClGpnseKVIxpmcjrj7MNnI0bnlfKTVQ=
|
||||||
go.uber.org/zap v1.24.0 h1:FiJd5l1UOLj0wCgbSE0rwwXHzEdAZS6hiiSnxJN/D60=
|
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-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.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||||
golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
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.22.0 h1:gqSGLZqv+AI9lIQzniJ0nZDRG5GBPsSi+DRNHWNz6yA=
|
||||||
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=
|
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-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-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/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 (
|
import (
|
||||||
"context"
|
"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"
|
"code.30cm.net/digimon/app-cloudep-permission-server/internal/svc"
|
||||||
|
|
||||||
"github.com/zeromicro/go-zero/core/logx"
|
"github.com/zeromicro/go-zero/core/logx"
|
||||||
|
@ -24,8 +24,8 @@ func NewCancelOneTimeTokenLogic(ctx context.Context, svcCtx *svc.ServiceContext)
|
||||||
}
|
}
|
||||||
|
|
||||||
// CancelOneTimeToken 取消一次性使用
|
// 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
|
// 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 (
|
import (
|
||||||
"context"
|
"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"
|
"code.30cm.net/digimon/app-cloudep-permission-server/internal/svc"
|
||||||
|
|
||||||
"github.com/zeromicro/go-zero/core/logx"
|
"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
|
// 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
|
// 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 (
|
import (
|
||||||
"context"
|
"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"
|
"code.30cm.net/digimon/app-cloudep-permission-server/internal/svc"
|
||||||
|
|
||||||
"github.com/zeromicro/go-zero/core/logx"
|
"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
|
// 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
|
// 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 (
|
import (
|
||||||
"context"
|
"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"
|
"code.30cm.net/digimon/app-cloudep-permission-server/internal/svc"
|
||||||
|
|
||||||
"github.com/zeromicro/go-zero/core/logx"
|
"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 都要登出
|
// 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
|
// 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 (
|
import (
|
||||||
"context"
|
"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"
|
"code.30cm.net/digimon/app-cloudep-permission-server/internal/svc"
|
||||||
|
|
||||||
"github.com/zeromicro/go-zero/core/logx"
|
"github.com/zeromicro/go-zero/core/logx"
|
||||||
|
@ -24,8 +24,8 @@ func NewGetUserTokensByDeviceIdLogic(ctx context.Context, svcCtx *svc.ServiceCon
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetUserTokensByDeviceId 取得目前所對應的 DeviceID 所存在的 Tokens
|
// 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
|
// 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 (
|
import (
|
||||||
"context"
|
"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"
|
"code.30cm.net/digimon/app-cloudep-permission-server/internal/svc"
|
||||||
|
|
||||||
"github.com/zeromicro/go-zero/core/logx"
|
"github.com/zeromicro/go-zero/core/logx"
|
||||||
|
@ -24,8 +24,8 @@ func NewGetUserTokensByUidLogic(ctx context.Context, svcCtx *svc.ServiceContext)
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetUserTokensByUid 取得目前所對應的 UID 所存在的 Tokens
|
// 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
|
// 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 (
|
import (
|
||||||
"context"
|
"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"
|
"code.30cm.net/digimon/app-cloudep-permission-server/internal/svc"
|
||||||
|
|
||||||
"github.com/zeromicro/go-zero/core/logx"
|
"github.com/zeromicro/go-zero/core/logx"
|
||||||
|
@ -24,8 +24,8 @@ func NewNewOneTimeTokenLogic(ctx context.Context, svcCtx *svc.ServiceContext) *N
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewOneTimeToken 建立一次性使用,例如:RefreshToken
|
// 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
|
// 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 (
|
import (
|
||||||
"context"
|
"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"
|
"code.30cm.net/digimon/app-cloudep-permission-server/internal/svc"
|
||||||
|
|
||||||
"github.com/zeromicro/go-zero/core/logx"
|
"github.com/zeromicro/go-zero/core/logx"
|
||||||
|
@ -24,8 +24,8 @@ func NewNewTokenLogic(ctx context.Context, svcCtx *svc.ServiceContext) *NewToken
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewToken 建立一個新的 Token,例如:AccessToken
|
// 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
|
// 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 (
|
import (
|
||||||
"context"
|
"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"
|
"code.30cm.net/digimon/app-cloudep-permission-server/internal/svc"
|
||||||
|
|
||||||
"github.com/zeromicro/go-zero/core/logx"
|
"github.com/zeromicro/go-zero/core/logx"
|
||||||
|
@ -24,8 +24,8 @@ func NewRefreshTokenLogic(ctx context.Context, svcCtx *svc.ServiceContext) *Refr
|
||||||
}
|
}
|
||||||
|
|
||||||
// RefreshToken 更新目前的token 以及裡面包含的一次性 Token
|
// 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
|
// 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 (
|
import (
|
||||||
"context"
|
"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"
|
"code.30cm.net/digimon/app-cloudep-permission-server/internal/svc"
|
||||||
|
|
||||||
"github.com/zeromicro/go-zero/core/logx"
|
"github.com/zeromicro/go-zero/core/logx"
|
||||||
|
@ -24,8 +24,8 @@ func NewValidationTokenLogic(ctx context.Context, svcCtx *svc.ServiceContext) *V
|
||||||
}
|
}
|
||||||
|
|
||||||
// ValidationToken 驗證這個 Token 有沒有效
|
// 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
|
// 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 (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
|
||||||
"code.30cm.net/digimon/app-cloudep-permission-server/gen_result/pb/app-cloudep-permission-server"
|
tokenservicelogic "code.30cm.net/digimon/app-cloudep-permission-server/internal/logic/tokenservice"
|
||||||
"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"
|
"code.30cm.net/digimon/app-cloudep-permission-server/internal/svc"
|
||||||
)
|
)
|
||||||
|
|
||||||
type TokenServiceServer struct {
|
type TokenServiceServer struct {
|
||||||
svcCtx *svc.ServiceContext
|
svcCtx *svc.ServiceContext
|
||||||
app_cloudep_permission_server.UnimplementedTokenServiceServer
|
permission.UnimplementedTokenServiceServer
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewTokenServiceServer(svcCtx *svc.ServiceContext) *TokenServiceServer {
|
func NewTokenServiceServer(svcCtx *svc.ServiceContext) *TokenServiceServer {
|
||||||
|
@ -24,61 +25,61 @@ func NewTokenServiceServer(svcCtx *svc.ServiceContext) *TokenServiceServer {
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewToken 建立一個新的 Token,例如:AccessToken
|
// 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)
|
l := tokenservicelogic.NewNewTokenLogic(ctx, s.svcCtx)
|
||||||
return l.NewToken(in)
|
return l.NewToken(in)
|
||||||
}
|
}
|
||||||
|
|
||||||
// RefreshToken 更新目前的token 以及裡面包含的一次性 Token
|
// 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)
|
l := tokenservicelogic.NewRefreshTokenLogic(ctx, s.svcCtx)
|
||||||
return l.RefreshToken(in)
|
return l.RefreshToken(in)
|
||||||
}
|
}
|
||||||
|
|
||||||
// CancelToken 取消 Token,也包含他裡面的 One Time Toke
|
// 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)
|
l := tokenservicelogic.NewCancelTokenLogic(ctx, s.svcCtx)
|
||||||
return l.CancelToken(in)
|
return l.CancelToken(in)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ValidationToken 驗證這個 Token 有沒有效
|
// 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)
|
l := tokenservicelogic.NewValidationTokenLogic(ctx, s.svcCtx)
|
||||||
return l.ValidationToken(in)
|
return l.ValidationToken(in)
|
||||||
}
|
}
|
||||||
|
|
||||||
// CancelTokens 取消 Token 從UID 視角,以及 token id 視角出發, UID 登出,底下所有 Device ID 也要登出, Token ID 登出, 所有 UID + Device 都要登出
|
// 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)
|
l := tokenservicelogic.NewCancelTokensLogic(ctx, s.svcCtx)
|
||||||
return l.CancelTokens(in)
|
return l.CancelTokens(in)
|
||||||
}
|
}
|
||||||
|
|
||||||
// CancelTokenByDeviceId 取消 Token, 從 Device 視角出發,可以選,登出這個Device 下所有 token ,登出這個Device 下指定token
|
// 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)
|
l := tokenservicelogic.NewCancelTokenByDeviceIdLogic(ctx, s.svcCtx)
|
||||||
return l.CancelTokenByDeviceId(in)
|
return l.CancelTokenByDeviceId(in)
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetUserTokensByDeviceId 取得目前所對應的 DeviceID 所存在的 Tokens
|
// 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)
|
l := tokenservicelogic.NewGetUserTokensByDeviceIdLogic(ctx, s.svcCtx)
|
||||||
return l.GetUserTokensByDeviceId(in)
|
return l.GetUserTokensByDeviceId(in)
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetUserTokensByUid 取得目前所對應的 UID 所存在的 Tokens
|
// 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)
|
l := tokenservicelogic.NewGetUserTokensByUidLogic(ctx, s.svcCtx)
|
||||||
return l.GetUserTokensByUid(in)
|
return l.GetUserTokensByUid(in)
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewOneTimeToken 建立一次性使用,例如:RefreshToken
|
// 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)
|
l := tokenservicelogic.NewNewOneTimeTokenLogic(ctx, s.svcCtx)
|
||||||
return l.NewOneTimeToken(in)
|
return l.NewOneTimeToken(in)
|
||||||
}
|
}
|
||||||
|
|
||||||
// CancelOneTimeToken 取消一次性使用
|
// 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)
|
l := tokenservicelogic.NewCancelOneTimeTokenLogic(ctx, s.svcCtx)
|
||||||
return l.CancelOneTimeToken(in)
|
return l.CancelOneTimeToken(in)
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,7 +4,7 @@ import (
|
||||||
"flag"
|
"flag"
|
||||||
"fmt"
|
"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"
|
"code.30cm.net/digimon/app-cloudep-permission-server/internal/config"
|
||||||
tokenserviceServer "code.30cm.net/digimon/app-cloudep-permission-server/internal/server/tokenservice"
|
tokenserviceServer "code.30cm.net/digimon/app-cloudep-permission-server/internal/server/tokenservice"
|
||||||
"code.30cm.net/digimon/app-cloudep-permission-server/internal/svc"
|
"code.30cm.net/digimon/app-cloudep-permission-server/internal/svc"
|
||||||
|
@ -26,7 +26,7 @@ func main() {
|
||||||
ctx := svc.NewServiceContext(c)
|
ctx := svc.NewServiceContext(c)
|
||||||
|
|
||||||
s := zrpc.MustNewServer(c.RpcServerConf, func(grpcServer *grpc.Server) {
|
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 {
|
if c.Mode == service.DevMode || c.Mode == service.TestMode {
|
||||||
reflection.Register(grpcServer)
|
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
|
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
|
package repository
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"code.30cm.net/digimon/app-cloudep-permission-server/pkg/domain/entity"
|
|
||||||
"context"
|
"context"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"code.30cm.net/digimon/app-cloudep-permission-server/pkg/domain/entity"
|
||||||
)
|
)
|
||||||
|
|
||||||
// TokenRepo 管理Token
|
// 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
|
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 {
|
type TokenUseCase interface {
|
||||||
// NewToken 創建新 Token,通常為 Access Token
|
ParseClaims
|
||||||
NewToken(ctx context.Context, req AuthorizationReq) (TokenResp, error)
|
// GenerateAccessToken 產生新的 Access Token
|
||||||
// RefreshToken 刷新目前的 Token,包括一次性 Token
|
GenerateAccessToken(ctx context.Context, req GenerateTokenRequest) (AccessTokenResponse, error)
|
||||||
RefreshToken(ctx context.Context, req RefreshTokenReq) (RefreshTokenResp, error)
|
// RefreshAccessToken 使用 Refresh Token 更新 Access Token(刷新令牌)
|
||||||
// CancelToken 取消 Token,包括取消其關聯的 One-Time Token
|
RefreshAccessToken(ctx context.Context, req RefreshTokenRequest) (RefreshTokenResponse, error)
|
||||||
CancelToken(ctx context.Context, req CancelTokenReq) error
|
// RevokeToken 撤銷單個 Token
|
||||||
// ValidationToken 驗證 Token 是否有效
|
RevokeToken(ctx context.Context, req TokenRequest) error
|
||||||
ValidationToken(ctx context.Context, req ValidationTokenReq) (ValidationTokenResp, error)
|
// VerifyToken 驗證 Token 是否有效
|
||||||
// CancelTokens 根據 UID 或 Token ID 取消所有相關 Token,通常在用戶登出時使用
|
VerifyToken(ctx context.Context, req TokenRequest) (VerifyTokenResponse, error)
|
||||||
CancelTokens(ctx context.Context, req DoTokenByUIDReq) error
|
// RevokeTokensByUID 根據 UID 撤銷所有 Token
|
||||||
// CancelTokenByDeviceID 根據 Device ID 取消所有相關的 Token
|
RevokeTokensByUID(ctx context.Context, req RevokeTokensByUIDRequest) error
|
||||||
CancelTokenByDeviceID(ctx context.Context, req DoTokenByDeviceIDReq) error
|
// RevokeTokensByDeviceID 根據 Device ID 取消所有相關的 Token
|
||||||
// GetUserTokensByDeviceID 根據 Device ID 獲取所有 Token
|
RevokeTokensByDeviceID(ctx context.Context, deviceID string) error
|
||||||
GetUserTokensByDeviceID(ctx context.Context, req DoTokenByDeviceIDReq) ([]*TokenResp, error)
|
// GetUserTokensByDeviceID 根據 Device ID 獲取所有 AccessToken
|
||||||
// GetUserTokensByUID 根據 UID 獲取所有 Token
|
GetUserTokensByDeviceID(ctx context.Context, deviceID string) ([]*AccessTokenResponse, error)
|
||||||
GetUserTokensByUID(ctx context.Context, req QueryTokenByUIDReq) ([]*TokenResp, error)
|
// GetUserTokensByUID 根據 UID 獲取所有 AccessToken
|
||||||
// NewOneTimeToken 創建一次性 Token,例如 Refresh Token
|
GetUserTokensByUID(ctx context.Context, uid string) ([]*AccessTokenResponse, error)
|
||||||
NewOneTimeToken(ctx context.Context, req CreateOneTimeTokenReq) (CreateOneTimeTokenResp, error)
|
|
||||||
// CancelOneTimeToken 取消一次性 Token
|
|
||||||
CancelOneTimeToken(ctx context.Context, req CancelOneTimeTokenReq) error
|
|
||||||
// ReadTokenBasicData 檢查Token 帶的資料
|
// ReadTokenBasicData 檢查Token 帶的資料
|
||||||
ReadTokenBasicData(ctx context.Context, token string) (map[string]string, error)
|
ReadTokenBasicData(ctx context.Context, token string) (Additional, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
// AuthorizationReq 定義授權請求的結構
|
type ParseClaims interface {
|
||||||
type AuthorizationReq struct {
|
// CreateAccessToken 建立 access token
|
||||||
GrantType string `json:"grant_type"` // 授權類型
|
CreateAccessToken(token entity.Token, data any, secretKey string) (string, error)
|
||||||
DeviceID string `json:"device_id"` // 設備 ID
|
// CreateRefreshToken 建立 RefreshToken
|
||||||
Scope string `json:"scope"` // 授權範圍
|
CreateRefreshToken(accessToken string) string
|
||||||
Data map[string]string `json:"data"` // 附加數據
|
// ParseJWTClaimsByAccessToken 使用Access Token 解析出 JWT 資訊
|
||||||
Expires int64 `json:"expires"` // 過期時間(秒)
|
ParseJWTClaimsByAccessToken(accessToken string, secret string, validate bool) (jwt.MapClaims, error)
|
||||||
IsRefreshToken bool `json:"is_refresh_token"` // 是否為刷新令牌
|
// ParseSystemClaimsByAccessToken 使用Access Token 解析出 系統資訊
|
||||||
Role string `json:"role"` // 是否為刷新令牌
|
ParseSystemClaimsByAccessToken(accessToken string, secret string, validate bool) (map[string]string, error)
|
||||||
Account string `json:"account"` // 登入時的帳號
|
}
|
||||||
|
|
||||||
|
// GenerateTokenRequest 定義授權請求的結構
|
||||||
|
type GenerateTokenRequest struct {
|
||||||
|
TokenType string `json:"token_type"` // 告訴前端Token 類型
|
||||||
|
DeviceID string `json:"device_id"` // 設備 ID
|
||||||
|
Scope string `json:"scope"` // 授權範圍
|
||||||
|
Expires int64 `json:"expires"` // 指定過期時間 UnixNano UTC 時間(沒給 = now 加設定秒數)
|
||||||
|
RefreshExpires int64 `json:"refresh_expires"` // 指定過期時間 UnixNano UTC 時間(沒給 = now 加設定秒數)
|
||||||
|
Role string `json:"role"` // 是否為刷新令牌
|
||||||
|
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
|
package repository
|
||||||
|
|
||||||
import (
|
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"
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/zeromicro/go-zero/core/stores/redis"
|
|
||||||
"time"
|
"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 需要的參數
|
// TokenRepositoryParam token 需要的參數
|
||||||
|
|
|
@ -1,16 +1,17 @@
|
||||||
package repository
|
package repository
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"code.30cm.net/digimon/app-cloudep-permission-server/pkg/domain"
|
|
||||||
"code.30cm.net/digimon/app-cloudep-permission-server/pkg/domain/entity"
|
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"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/alicebob/miniredis/v2"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/zeromicro/go-zero/core/stores/redis"
|
"github.com/zeromicro/go-zero/core/stores/redis"
|
||||||
"testing"
|
|
||||||
"time"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func setupMiniRedis() (*miniredis.Miniredis, *redis.Redis) {
|
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