diff --git a/client/permissionservice/permission_service.go b/client/permissionservice/permission_service.go new file mode 100644 index 0000000..fdae9cb --- /dev/null +++ b/client/permissionservice/permission_service.go @@ -0,0 +1,45 @@ +// Code generated by goctl. DO NOT EDIT. +// Source: permission.proto + +package permissionservice + +import ( + "context" + + "ark-permission/gen_result/pb/permission" + + "github.com/zeromicro/go-zero/zrpc" + "google.golang.org/grpc" +) + +type ( + AuthorizationReq = permission.AuthorizationReq + CancelOneTimeTokenReq = permission.CancelOneTimeTokenReq + CancelTokenReq = permission.CancelTokenReq + CreateOneTimeTokenReq = permission.CreateOneTimeTokenReq + CreateOneTimeTokenResp = permission.CreateOneTimeTokenResp + DoTokenByDeviceIDReq = permission.DoTokenByDeviceIDReq + DoTokenByUIDReq = permission.DoTokenByUIDReq + OKResp = permission.OKResp + QueryTokenByUIDReq = permission.QueryTokenByUIDReq + RefreshTokenReq = permission.RefreshTokenReq + RefreshTokenResp = permission.RefreshTokenResp + Token = permission.Token + TokenResp = permission.TokenResp + Tokens = permission.Tokens + ValidationTokenReq = permission.ValidationTokenReq + ValidationTokenResp = permission.ValidationTokenResp + + PermissionService interface { + } + + defaultPermissionService struct { + cli zrpc.Client + } +) + +func NewPermissionService(cli zrpc.Client) PermissionService { + return &defaultPermissionService{ + cli: cli, + } +} diff --git a/client/roleservice/role_service.go b/client/roleservice/role_service.go new file mode 100644 index 0000000..8d7e2e2 --- /dev/null +++ b/client/roleservice/role_service.go @@ -0,0 +1,51 @@ +// Code generated by goctl. DO NOT EDIT. +// Source: permission.proto + +package roleservice + +import ( + "context" + + "ark-permission/gen_result/pb/permission" + + "github.com/zeromicro/go-zero/zrpc" + "google.golang.org/grpc" +) + +type ( + AuthorizationReq = permission.AuthorizationReq + CancelOneTimeTokenReq = permission.CancelOneTimeTokenReq + CancelTokenReq = permission.CancelTokenReq + CreateOneTimeTokenReq = permission.CreateOneTimeTokenReq + CreateOneTimeTokenResp = permission.CreateOneTimeTokenResp + DoTokenByDeviceIDReq = permission.DoTokenByDeviceIDReq + DoTokenByUIDReq = permission.DoTokenByUIDReq + OKResp = permission.OKResp + QueryTokenByUIDReq = permission.QueryTokenByUIDReq + RefreshTokenReq = permission.RefreshTokenReq + RefreshTokenResp = permission.RefreshTokenResp + Token = permission.Token + TokenResp = permission.TokenResp + Tokens = permission.Tokens + ValidationTokenReq = permission.ValidationTokenReq + ValidationTokenResp = permission.ValidationTokenResp + + RoleService interface { + Ping(ctx context.Context, in *OKResp, opts ...grpc.CallOption) (*OKResp, error) + } + + defaultRoleService struct { + cli zrpc.Client + } +) + +func NewRoleService(cli zrpc.Client) RoleService { + return &defaultRoleService{ + cli: cli, + } +} + +func (m *defaultRoleService) Ping(ctx context.Context, in *OKResp, opts ...grpc.CallOption) (*OKResp, error) { + client := permission.NewRoleServiceClient(m.cli.Conn()) + return client.Ping(ctx, in, opts...) +} diff --git a/client/tokenservice/token_service.go b/client/tokenservice/token_service.go new file mode 100644 index 0000000..2bc1af2 --- /dev/null +++ b/client/tokenservice/token_service.go @@ -0,0 +1,125 @@ +// Code generated by goctl. DO NOT EDIT. +// Source: permission.proto + +package tokenservice + +import ( + "context" + + "ark-permission/gen_result/pb/permission" + + "github.com/zeromicro/go-zero/zrpc" + "google.golang.org/grpc" +) + +type ( + AuthorizationReq = permission.AuthorizationReq + CancelOneTimeTokenReq = permission.CancelOneTimeTokenReq + CancelTokenReq = permission.CancelTokenReq + CreateOneTimeTokenReq = permission.CreateOneTimeTokenReq + CreateOneTimeTokenResp = permission.CreateOneTimeTokenResp + DoTokenByDeviceIDReq = permission.DoTokenByDeviceIDReq + DoTokenByUIDReq = permission.DoTokenByUIDReq + OKResp = permission.OKResp + QueryTokenByUIDReq = permission.QueryTokenByUIDReq + RefreshTokenReq = permission.RefreshTokenReq + RefreshTokenResp = permission.RefreshTokenResp + Token = permission.Token + TokenResp = permission.TokenResp + Tokens = permission.Tokens + ValidationTokenReq = permission.ValidationTokenReq + ValidationTokenResp = permission.ValidationTokenResp + + TokenService interface { + // NewToken 建立一個新的 Token,例如:AccessToken + NewToken(ctx context.Context, in *AuthorizationReq, opts ...grpc.CallOption) (*TokenResp, error) + // RefreshToken 更新目前的token 以及裡面包含的一次性 Token + RefreshToken(ctx context.Context, in *RefreshTokenReq, opts ...grpc.CallOption) (*RefreshTokenResp, error) + // CancelToken 取消 Token,也包含他裡面的 One Time Toke + CancelToken(ctx context.Context, in *CancelTokenReq, opts ...grpc.CallOption) (*OKResp, error) + // CancelTokenByUid 取消 Token (取消這個用戶從不同 Device 登入的所有 Token),也包含他裡面的 One Time Toke + CancelTokenByUid(ctx context.Context, in *DoTokenByUIDReq, opts ...grpc.CallOption) (*OKResp, error) + // CancelTokenByDeviceId 取消 Token + CancelTokenByDeviceId(ctx context.Context, in *DoTokenByDeviceIDReq, opts ...grpc.CallOption) (*OKResp, error) + // ValidationToken 驗證這個 Token 有沒有效 + ValidationToken(ctx context.Context, in *ValidationTokenReq, opts ...grpc.CallOption) (*ValidationTokenResp, error) + // GetUserTokensByDeviceId 取得目前所對應的 DeviceID 所存在的 Tokens + GetUserTokensByDeviceId(ctx context.Context, in *DoTokenByDeviceIDReq, opts ...grpc.CallOption) (*Tokens, error) + // GetUserTokensByUid 取得目前所對應的 UID 所存在的 Tokens + GetUserTokensByUid(ctx context.Context, in *QueryTokenByUIDReq, opts ...grpc.CallOption) (*Tokens, error) + // NewOneTimeToken 建立一次性使用,例如:RefreshToken + NewOneTimeToken(ctx context.Context, in *CreateOneTimeTokenReq, opts ...grpc.CallOption) (*CreateOneTimeTokenResp, error) + // CancelOneTimeToken 取消一次性使用 + CancelOneTimeToken(ctx context.Context, in *CancelOneTimeTokenReq, opts ...grpc.CallOption) (*OKResp, error) + } + + defaultTokenService struct { + cli zrpc.Client + } +) + +func NewTokenService(cli zrpc.Client) TokenService { + return &defaultTokenService{ + cli: cli, + } +} + +// NewToken 建立一個新的 Token,例如:AccessToken +func (m *defaultTokenService) NewToken(ctx context.Context, in *AuthorizationReq, opts ...grpc.CallOption) (*TokenResp, error) { + client := permission.NewTokenServiceClient(m.cli.Conn()) + return client.NewToken(ctx, in, opts...) +} + +// RefreshToken 更新目前的token 以及裡面包含的一次性 Token +func (m *defaultTokenService) RefreshToken(ctx context.Context, in *RefreshTokenReq, opts ...grpc.CallOption) (*RefreshTokenResp, error) { + client := permission.NewTokenServiceClient(m.cli.Conn()) + return client.RefreshToken(ctx, in, opts...) +} + +// CancelToken 取消 Token,也包含他裡面的 One Time Toke +func (m *defaultTokenService) CancelToken(ctx context.Context, in *CancelTokenReq, opts ...grpc.CallOption) (*OKResp, error) { + client := permission.NewTokenServiceClient(m.cli.Conn()) + return client.CancelToken(ctx, in, opts...) +} + +// CancelTokenByUid 取消 Token (取消這個用戶從不同 Device 登入的所有 Token),也包含他裡面的 One Time Toke +func (m *defaultTokenService) CancelTokenByUid(ctx context.Context, in *DoTokenByUIDReq, opts ...grpc.CallOption) (*OKResp, error) { + client := permission.NewTokenServiceClient(m.cli.Conn()) + return client.CancelTokenByUid(ctx, in, opts...) +} + +// CancelTokenByDeviceId 取消 Token +func (m *defaultTokenService) CancelTokenByDeviceId(ctx context.Context, in *DoTokenByDeviceIDReq, opts ...grpc.CallOption) (*OKResp, error) { + client := permission.NewTokenServiceClient(m.cli.Conn()) + return client.CancelTokenByDeviceId(ctx, in, opts...) +} + +// ValidationToken 驗證這個 Token 有沒有效 +func (m *defaultTokenService) ValidationToken(ctx context.Context, in *ValidationTokenReq, opts ...grpc.CallOption) (*ValidationTokenResp, error) { + client := permission.NewTokenServiceClient(m.cli.Conn()) + return client.ValidationToken(ctx, in, opts...) +} + +// GetUserTokensByDeviceId 取得目前所對應的 DeviceID 所存在的 Tokens +func (m *defaultTokenService) GetUserTokensByDeviceId(ctx context.Context, in *DoTokenByDeviceIDReq, opts ...grpc.CallOption) (*Tokens, error) { + client := permission.NewTokenServiceClient(m.cli.Conn()) + return client.GetUserTokensByDeviceId(ctx, in, opts...) +} + +// GetUserTokensByUid 取得目前所對應的 UID 所存在的 Tokens +func (m *defaultTokenService) GetUserTokensByUid(ctx context.Context, in *QueryTokenByUIDReq, opts ...grpc.CallOption) (*Tokens, error) { + client := permission.NewTokenServiceClient(m.cli.Conn()) + return client.GetUserTokensByUid(ctx, in, opts...) +} + +// NewOneTimeToken 建立一次性使用,例如:RefreshToken +func (m *defaultTokenService) NewOneTimeToken(ctx context.Context, in *CreateOneTimeTokenReq, opts ...grpc.CallOption) (*CreateOneTimeTokenResp, error) { + client := permission.NewTokenServiceClient(m.cli.Conn()) + return client.NewOneTimeToken(ctx, in, opts...) +} + +// CancelOneTimeToken 取消一次性使用 +func (m *defaultTokenService) CancelOneTimeToken(ctx context.Context, in *CancelOneTimeTokenReq, opts ...grpc.CallOption) (*OKResp, error) { + client := permission.NewTokenServiceClient(m.cli.Conn()) + return client.CancelOneTimeToken(ctx, in, opts...) +} diff --git a/generate/protobuf/permission.proto b/generate/protobuf/permission.proto index 717f753..38bfdc0 100644 --- a/generate/protobuf/permission.proto +++ b/generate/protobuf/permission.proto @@ -68,7 +68,13 @@ message CancelTokenReq { // CancelTokenReq 註銷這個 Token message DoTokenByUIDReq { - repeated string uid = 1; + repeated string ids = 1; + string uid = 2; +} + +// QueryTokenByUIDReq 拿這個UID 找 Token +message QueryTokenByUIDReq { + string uid = 1; } // ValidationTokenReq 驗證這個 Token @@ -112,6 +118,10 @@ message DoTokenByDeviceIDReq { } message Tokens{ + repeated TokenResp token = 1; +} + +message CancelOneTimeTokenReq { repeated string token = 1; } @@ -125,23 +135,29 @@ service TokenService { rpc RefreshToken(RefreshTokenReq) returns(RefreshTokenResp); // CancelToken 取消 Token,也包含他裡面的 One Time Toke rpc CancelToken(CancelTokenReq) returns(OKResp); - // CancelTokenByUID 取消 Token (取消這個用戶從不同 Device 登入的所有 Token),也包含他裡面的 One Time Toke - rpc CancelTokenByUid(DoTokenByUIDReq) returns(OKResp); - // CancelTokenByDeviceID 取消 Token - rpc CancelTokenByDeviceId(DoTokenByDeviceIDReq) returns(OKResp); // ValidationToken 驗證這個 Token 有沒有效 rpc ValidationToken(ValidationTokenReq) returns(ValidationTokenResp); - // GetUserTokensByDeviceIDs 取得目前所對應的 DeviceID 所存在的 Tokens + // CancelTokens 取消 Token 從UID 視角,以及 token id 視角出發, UID 登出,底下所有 Device ID 也要登出, Token ID 登出, 所有 UID + Device 都要登出 + rpc CancelTokens(DoTokenByUIDReq) returns(OKResp); + + // CancelTokenByDeviceId 取消 Token, 從 Device 視角出發,可以選,登出這個Device 下所有 token ,登出這個Device 下指定token + rpc CancelTokenByDeviceId(DoTokenByDeviceIDReq) returns(OKResp); + // GetUserTokensByDeviceId 取得目前所對應的 DeviceID 所存在的 Tokens rpc GetUserTokensByDeviceId(DoTokenByDeviceIDReq) returns(Tokens); - // GetUserTokensByUID 取得目前所對應的 UID 所存在的 Tokens - rpc GetUserTokensByUid(DoTokenByUIDReq) returns(Tokens); + + + // GetUserTokensByUid 取得目前所對應的 UID 所存在的 Tokens + rpc GetUserTokensByUid(QueryTokenByUIDReq) returns(Tokens); + // NewOneTimeToken 建立一次性使用,例如:RefreshToken rpc NewOneTimeToken(CreateOneTimeTokenReq) returns(CreateOneTimeTokenResp); // CancelOneTimeToken 取消一次性使用 - rpc CancelOneTimeToken(CreateOneTimeTokenReq) returns(CreateOneTimeTokenResp); + rpc CancelOneTimeToken(CancelOneTimeTokenReq) returns(OKResp); } -//service Role_Service {} -// -//service Permission_Service {} \ No newline at end of file +service RoleService { + rpc Ping(OKResp) returns(OKResp); +} + +service PermissionService {} \ No newline at end of file diff --git a/internal/domain/errors.go b/internal/domain/errors.go index 0ccbdfd..3182133 100644 --- a/internal/domain/errors.go +++ b/internal/domain/errors.go @@ -22,6 +22,7 @@ const ( const ( RedisDelErrorCode = iota + 20 RedisPipLineErrorCode + RedisErrorCode ) // TokenUnexpectedSigningErr 30001 Token 簽名錯誤 @@ -53,5 +54,12 @@ func RedisDelError(msg string) *ers.Err { func RedisPipLineError(msg string) *ers.Err { // 看需要建立哪些 Metrics mts.AppErrorMetrics.AddFailure("redis", "pip_line_error") - return ers.NewErr(code.CloudEPPermission, code.CatInput, TokenClaimErrorCode, msg) + return ers.NewErr(code.CloudEPPermission, code.CatInput, RedisPipLineErrorCode, msg) +} + +// RedisError 30022 Redis 錯誤 +func RedisError(msg string) *ers.Err { + // 看需要建立哪些 Metrics + mts.AppErrorMetrics.AddFailure("redis", "error") + return ers.NewErr(code.CloudEPPermission, code.CatInput, RedisErrorCode, msg) } diff --git a/internal/domain/repository/token.go b/internal/domain/repository/token.go index 8d7de3e..57bd4f6 100644 --- a/internal/domain/repository/token.go +++ b/internal/domain/repository/token.go @@ -3,10 +3,30 @@ package repository import ( "ark-permission/internal/entity" "context" + "time" ) type TokenRepository interface { Create(ctx context.Context, token entity.Token) error - GetByAccess(ctx context.Context, id string) (entity.Token, error) + DeleteOneTimeToken(ctx context.Context, ids []string, tokens []entity.Token) error + CreateOneTimeToken(ctx context.Context, key string, ticket entity.Ticket, dt time.Duration) error + GetByRefresh(ctx context.Context, refreshToken string) (entity.Token, error) + + GetAccessTokenByID(ctx context.Context, id string) (entity.Token, error) + GetAccessTokensByUID(ctx context.Context, uid string) ([]entity.Token, error) + GetAccessTokenCountByUID(uid string) (int, error) + GetAccessTokensByDeviceID(ctx context.Context, deviceID string) ([]entity.Token, error) + GetAccessTokenCountByDeviceID(deviceID string) (int, error) + Delete(ctx context.Context, token entity.Token) error + DeleteAccessTokenByID(ctx context.Context, id string) error + DeleteAccessTokensByUID(ctx context.Context, uid string) error + DeleteAccessTokensByDeviceID(ctx context.Context, deviceID string) error + DeleteAccessTokenByDeviceIDAndUID(ctx context.Context, deviceID, uid string) error + DeleteUIDToken(ctx context.Context, uid string, ids []string) error +} + +type DeviceToken struct { + DeviceID string + TokenID string } diff --git a/internal/entity/token.go b/internal/entity/token.go index eeadd26..c2cd7fc 100644 --- a/internal/entity/token.go +++ b/internal/entity/token.go @@ -33,6 +33,6 @@ func (t *Token) IsExpires() bool { type UIDToken map[string]int64 type Ticket struct { - Data interface{} `json:"data"` - Token Token `json:"token"` + Data any `json:"data"` + Token Token `json:"token"` } diff --git a/internal/logic/get_user_tokens_by_uid_logic.go b/internal/logic/get_user_tokens_by_uid_logic.go deleted file mode 100644 index ee70ab1..0000000 --- a/internal/logic/get_user_tokens_by_uid_logic.go +++ /dev/null @@ -1,31 +0,0 @@ -package logic - -import ( - "context" - - "ark-permission/gen_result/pb/permission" - "ark-permission/internal/svc" - - "github.com/zeromicro/go-zero/core/logx" -) - -type GetUserTokensByUidLogic struct { - ctx context.Context - svcCtx *svc.ServiceContext - logx.Logger -} - -func NewGetUserTokensByUidLogic(ctx context.Context, svcCtx *svc.ServiceContext) *GetUserTokensByUidLogic { - return &GetUserTokensByUidLogic{ - ctx: ctx, - svcCtx: svcCtx, - Logger: logx.WithContext(ctx), - } -} - -// GetUserTokensByUID 取得目前所對應的 UID 所存在的 Tokens -func (l *GetUserTokensByUidLogic) GetUserTokensByUid(in *permission.DoTokenByUIDReq) (*permission.Tokens, error) { - // todo: add your logic here and delete this line - - return &permission.Tokens{}, nil -} diff --git a/internal/logic/new_one_time_token_logic.go b/internal/logic/new_one_time_token_logic.go deleted file mode 100644 index 7183ba4..0000000 --- a/internal/logic/new_one_time_token_logic.go +++ /dev/null @@ -1,31 +0,0 @@ -package logic - -import ( - "context" - - "ark-permission/gen_result/pb/permission" - "ark-permission/internal/svc" - - "github.com/zeromicro/go-zero/core/logx" -) - -type NewOneTimeTokenLogic struct { - ctx context.Context - svcCtx *svc.ServiceContext - logx.Logger -} - -func NewNewOneTimeTokenLogic(ctx context.Context, svcCtx *svc.ServiceContext) *NewOneTimeTokenLogic { - return &NewOneTimeTokenLogic{ - ctx: ctx, - svcCtx: svcCtx, - Logger: logx.WithContext(ctx), - } -} - -// NewOneTimeToken 建立一次性使用,例如:RefreshToken -func (l *NewOneTimeTokenLogic) NewOneTimeToken(in *permission.CreateOneTimeTokenReq) (*permission.CreateOneTimeTokenResp, error) { - // todo: add your logic here and delete this line - - return &permission.CreateOneTimeTokenResp{}, nil -} diff --git a/internal/logic/refresh_token_logic.go b/internal/logic/refresh_token_logic.go deleted file mode 100644 index c09cee8..0000000 --- a/internal/logic/refresh_token_logic.go +++ /dev/null @@ -1,30 +0,0 @@ -package logic - -import ( - "ark-permission/gen_result/pb/permission" - "ark-permission/internal/svc" - "context" - - "github.com/zeromicro/go-zero/core/logx" -) - -type RefreshTokenLogic struct { - ctx context.Context - svcCtx *svc.ServiceContext - logx.Logger -} - -func NewRefreshTokenLogic(ctx context.Context, svcCtx *svc.ServiceContext) *RefreshTokenLogic { - return &RefreshTokenLogic{ - ctx: ctx, - svcCtx: svcCtx, - Logger: logx.WithContext(ctx), - } -} - -// RefreshToken 更新目前的token 以及裡面包含的一次性 Token -func (l *RefreshTokenLogic) RefreshToken(in *permission.RefreshTokenReq) (*permission.RefreshTokenResp, error) { - // todo: add your logic here and delete this line - - return &permission.RefreshTokenResp{}, nil -} diff --git a/internal/logic/roleservice/ping_logic.go b/internal/logic/roleservice/ping_logic.go new file mode 100644 index 0000000..bbe1ccb --- /dev/null +++ b/internal/logic/roleservice/ping_logic.go @@ -0,0 +1,30 @@ +package roleservicelogic + +import ( + "context" + + "ark-permission/gen_result/pb/permission" + "ark-permission/internal/svc" + + "github.com/zeromicro/go-zero/core/logx" +) + +type PingLogic struct { + ctx context.Context + svcCtx *svc.ServiceContext + logx.Logger +} + +func NewPingLogic(ctx context.Context, svcCtx *svc.ServiceContext) *PingLogic { + return &PingLogic{ + ctx: ctx, + svcCtx: svcCtx, + Logger: logx.WithContext(ctx), + } +} + +func (l *PingLogic) Ping(in *permission.OKResp) (*permission.OKResp, error) { + // todo: add your logic here and delete this line + + return &permission.OKResp{}, nil +} diff --git a/internal/logic/cancel_one_time_token_logic.go b/internal/logic/tokenservice/cancel_one_time_token_logic.go similarity index 51% rename from internal/logic/cancel_one_time_token_logic.go rename to internal/logic/tokenservice/cancel_one_time_token_logic.go index 86e6d5e..86092eb 100644 --- a/internal/logic/cancel_one_time_token_logic.go +++ b/internal/logic/tokenservice/cancel_one_time_token_logic.go @@ -1,6 +1,7 @@ -package logic +package tokenservicelogic import ( + ers "code.30cm.net/wanderland/library-go/errors" "context" "ark-permission/gen_result/pb/permission" @@ -23,9 +24,23 @@ func NewCancelOneTimeTokenLogic(ctx context.Context, svcCtx *svc.ServiceContext) } } -// CancelOneTimeToken 取消一次性使用 -func (l *CancelOneTimeTokenLogic) CancelOneTimeToken(in *permission.CreateOneTimeTokenReq) (*permission.CreateOneTimeTokenResp, error) { - // todo: add your logic here and delete this line - - return &permission.CreateOneTimeTokenResp{}, nil +type cancelOneTimeTokenReq struct { + Token []string `json:"token" validate:"required"` +} + +// CancelOneTimeToken 取消一次性使用 +func (l *CancelOneTimeTokenLogic) CancelOneTimeToken(in *permission.CancelOneTimeTokenReq) (*permission.OKResp, error) { + // 驗證所需 + if err := l.svcCtx.Validate.ValidateAll(&cancelOneTimeTokenReq{ + Token: in.GetToken(), + }); err != nil { + return nil, ers.InvalidFormat(err.Error()) + } + + err := l.svcCtx.TokenRedisRepo.DeleteOneTimeToken(l.ctx, in.GetToken(), nil) + if err != nil { + return nil, err + } + + return &permission.OKResp{}, nil } diff --git a/internal/logic/cancel_token_by_device_id_logic.go b/internal/logic/tokenservice/cancel_token_by_device_id_logic.go similarity index 91% rename from internal/logic/cancel_token_by_device_id_logic.go rename to internal/logic/tokenservice/cancel_token_by_device_id_logic.go index 862a8d5..ed59824 100644 --- a/internal/logic/cancel_token_by_device_id_logic.go +++ b/internal/logic/tokenservice/cancel_token_by_device_id_logic.go @@ -1,4 +1,4 @@ -package logic +package tokenservicelogic import ( "context" @@ -23,7 +23,7 @@ func NewCancelTokenByDeviceIdLogic(ctx context.Context, svcCtx *svc.ServiceConte } } -// CancelTokenByDeviceID 取消 Token +// CancelTokenByDeviceId 取消 Token func (l *CancelTokenByDeviceIdLogic) CancelTokenByDeviceId(in *permission.DoTokenByDeviceIDReq) (*permission.OKResp, error) { // todo: add your logic here and delete this line diff --git a/internal/logic/cancel_token_by_uid_logic.go b/internal/logic/tokenservice/cancel_token_by_uid_logic.go similarity index 54% rename from internal/logic/cancel_token_by_uid_logic.go rename to internal/logic/tokenservice/cancel_token_by_uid_logic.go index 05b0566..f266185 100644 --- a/internal/logic/cancel_token_by_uid_logic.go +++ b/internal/logic/tokenservice/cancel_token_by_uid_logic.go @@ -1,6 +1,7 @@ -package logic +package tokenservicelogic import ( + ers "code.30cm.net/wanderland/library-go/errors" "context" "ark-permission/gen_result/pb/permission" @@ -23,9 +24,25 @@ func NewCancelTokenByUidLogic(ctx context.Context, svcCtx *svc.ServiceContext) * } } -// CancelTokenByUID 取消 Token (取消這個用戶從不同 Device 登入的所有 Token),也包含他裡面的 One Time Toke +type deleteByTokenIDs struct { + UID string `json:"uid" binding:"required"` + IDs []string `json:"ids" binding:"required"` +} + +// CancelTokenByUid 取消 Token (取消這個用戶從不同 Device 登入的所有 Token),也包含他裡面的 One Time Toke func (l *CancelTokenByUidLogic) CancelTokenByUid(in *permission.DoTokenByUIDReq) (*permission.OKResp, error) { - // todo: add your logic here and delete this line + // 驗證所需 + if err := l.svcCtx.Validate.ValidateAll(&deleteByTokenIDs{ + UID: in.GetUid(), + IDs: in.GetIds(), + }); err != nil { + return nil, ers.InvalidFormat(err.Error()) + } + + err := l.svcCtx.TokenRedisRepo.DeleteUIDToken(l.ctx, in.GetUid(), in.GetIds()) + if err != nil { + return nil, err + } return &permission.OKResp{}, nil } diff --git a/internal/logic/cancel_token_logic.go b/internal/logic/tokenservice/cancel_token_logic.go similarity index 98% rename from internal/logic/cancel_token_logic.go rename to internal/logic/tokenservice/cancel_token_logic.go index d7f94a2..4f615bd 100644 --- a/internal/logic/cancel_token_logic.go +++ b/internal/logic/tokenservice/cancel_token_logic.go @@ -1,10 +1,12 @@ -package logic +package tokenservicelogic import ( - "ark-permission/gen_result/pb/permission" - "ark-permission/internal/svc" ers "code.30cm.net/wanderland/library-go/errors" "context" + + "ark-permission/gen_result/pb/permission" + "ark-permission/internal/svc" + "github.com/zeromicro/go-zero/core/logx" ) diff --git a/internal/logic/get_user_tokens_by_device_id_logic.go b/internal/logic/tokenservice/get_user_tokens_by_device_id_logic.go similarity index 57% rename from internal/logic/get_user_tokens_by_device_id_logic.go rename to internal/logic/tokenservice/get_user_tokens_by_device_id_logic.go index d633995..93836ea 100644 --- a/internal/logic/get_user_tokens_by_device_id_logic.go +++ b/internal/logic/tokenservice/get_user_tokens_by_device_id_logic.go @@ -1,11 +1,9 @@ -package logic +package tokenservicelogic import ( - "context" - "ark-permission/gen_result/pb/permission" "ark-permission/internal/svc" - + "context" "github.com/zeromicro/go-zero/core/logx" ) @@ -23,9 +21,23 @@ func NewGetUserTokensByDeviceIdLogic(ctx context.Context, svcCtx *svc.ServiceCon } } -// GetUserTokensByDeviceIDs 取得目前所對應的 DeviceID 所存在的 Tokens +// GetUserTokensByDeviceId 取得目前所對應的 DeviceID 所存在的 Tokens func (l *GetUserTokensByDeviceIdLogic) GetUserTokensByDeviceId(in *permission.DoTokenByDeviceIDReq) (*permission.Tokens, error) { - // todo: add your logic here and delete this line + + // ids, err := l.svcCtx.TokenRedisRepo.GetAccessTokensByDeviceID(l.ctx, "") + // if err != nil { + // return nil, error + // } + + // tokenIDs := make([]usecase.DeviceToken, 0, len(ids)) + // for _, v := range ids { + // tokenIDs = append(tokenIDs, usecase.DeviceToken{ + // DeviceID: v.DeviceID, + // TokenID: v.TokenID, + // }) + // } + // + // return tokenIDs, nil return &permission.Tokens{}, nil } diff --git a/internal/logic/tokenservice/get_user_tokens_by_uid_logic.go b/internal/logic/tokenservice/get_user_tokens_by_uid_logic.go new file mode 100644 index 0000000..eb3a1f0 --- /dev/null +++ b/internal/logic/tokenservice/get_user_tokens_by_uid_logic.go @@ -0,0 +1,57 @@ +package tokenservicelogic + +import ( + "ark-permission/gen_result/pb/permission" + "ark-permission/internal/domain" + "ark-permission/internal/svc" + ers "code.30cm.net/wanderland/library-go/errors" + "context" + + "github.com/zeromicro/go-zero/core/logx" +) + +type GetUserTokensByUidLogic struct { + ctx context.Context + svcCtx *svc.ServiceContext + logx.Logger +} + +func NewGetUserTokensByUidLogic(ctx context.Context, svcCtx *svc.ServiceContext) *GetUserTokensByUidLogic { + return &GetUserTokensByUidLogic{ + ctx: ctx, + svcCtx: svcCtx, + Logger: logx.WithContext(ctx), + } +} + +type getUserTokensByUidReq struct { + UID string `json:"uid" validate:"required"` +} + +// GetUserTokensByUid 取得目前所對應的 UID 所存在的 Tokens +func (l *GetUserTokensByUidLogic) GetUserTokensByUid(in *permission.QueryTokenByUIDReq) (*permission.Tokens, error) { + if err := l.svcCtx.Validate.ValidateAll(&getUserTokensByUidReq{ + UID: in.GetUid(), + }); err != nil { + return nil, ers.InvalidFormat(err.Error()) + } + + uidTokens, err := l.svcCtx.TokenRedisRepo.GetAccessTokensByUID(l.ctx, in.GetUid()) + if err != nil { + return nil, err + } + + tokens := make([]*permission.TokenResp, 0, len(uidTokens)) + for _, v := range uidTokens { + tokens = append(tokens, &permission.TokenResp{ + AccessToken: v.AccessToken, + TokenType: domain.TokenTypeBearer, + ExpiresIn: int32(v.ExpiresIn), + RefreshToken: v.RefreshToken, + }) + } + + return &permission.Tokens{ + Token: tokens, + }, nil +} diff --git a/internal/logic/tokenservice/new_one_time_token_logic.go b/internal/logic/tokenservice/new_one_time_token_logic.go new file mode 100644 index 0000000..bcb02d5 --- /dev/null +++ b/internal/logic/tokenservice/new_one_time_token_logic.go @@ -0,0 +1,69 @@ +package tokenservicelogic + +import ( + "ark-permission/internal/domain" + "ark-permission/internal/entity" + ers "code.30cm.net/wanderland/library-go/errors" + "context" + "time" + + "ark-permission/gen_result/pb/permission" + "ark-permission/internal/svc" + + "github.com/zeromicro/go-zero/core/logx" +) + +type NewOneTimeTokenLogic struct { + ctx context.Context + svcCtx *svc.ServiceContext + logx.Logger +} + +func NewNewOneTimeTokenLogic(ctx context.Context, svcCtx *svc.ServiceContext) *NewOneTimeTokenLogic { + return &NewOneTimeTokenLogic{ + ctx: ctx, + svcCtx: svcCtx, + Logger: logx.WithContext(ctx), + } +} + +// NewOneTimeToken 建立一次性使用,例如:RefreshToken +func (l *NewOneTimeTokenLogic) NewOneTimeToken(in *permission.CreateOneTimeTokenReq) (*permission.CreateOneTimeTokenResp, error) { + // 驗證所需 + if err := l.svcCtx.Validate.ValidateAll(&refreshTokenReq{ + Token: in.GetToken(), + }); err != nil { + return nil, ers.InvalidFormat(err.Error()) + } + + // 驗證Token + claims, err := parseClaims(l.ctx, in.GetToken(), l.svcCtx.Config.Token.Secret) + if err != nil { + logx.WithCallerSkip(1).WithFields( + logx.Field("func", "parseClaims"), + ).Error(err.Error()) + return nil, err + } + + token, err := l.svcCtx.TokenRedisRepo.GetByAccess(l.ctx, claims.ID()) + if err != nil { + logx.WithCallerSkip(1).WithFields( + logx.Field("func", "TokenRedisRepo.GetByAccess"), + logx.Field("claims", claims), + ).Error(err.Error()) + return nil, err + } + + oneTimeToken := generateRefreshToken(in.GetToken()) + key := domain.TicketKeyPrefix + oneTimeToken + if err = l.svcCtx.TokenRedisRepo.CreateOneTimeToken(l.ctx, key, entity.Ticket{ + Data: claims, + Token: token, + }, time.Minute); err != nil { + return &permission.CreateOneTimeTokenResp{}, err + } + + return &permission.CreateOneTimeTokenResp{ + OneTimeToken: oneTimeToken, + }, nil +} diff --git a/internal/logic/new_token_logic.go b/internal/logic/tokenservice/new_token_logic.go similarity index 86% rename from internal/logic/new_token_logic.go rename to internal/logic/tokenservice/new_token_logic.go index 078bb33..487e3f9 100644 --- a/internal/logic/new_token_logic.go +++ b/internal/logic/tokenservice/new_token_logic.go @@ -1,15 +1,16 @@ -package logic +package tokenservicelogic import ( - "ark-permission/gen_result/pb/permission" "ark-permission/internal/domain" "ark-permission/internal/entity" - "ark-permission/internal/svc" ers "code.30cm.net/wanderland/library-go/errors" "context" "github.com/google/uuid" "time" + "ark-permission/gen_result/pb/permission" + "ark-permission/internal/svc" + "github.com/zeromicro/go-zero/core/logx" ) @@ -37,9 +38,6 @@ type authorizationReq struct { IsRefreshToken bool `json:"is_refresh_token"` } -var generateAccessTokenFunc = generateAccessToken -var generateRefreshTokenFunc = generateRefreshToken - // NewToken 建立一個新的 Token,例如:AccessToken func (l *NewTokenLogic) NewToken(in *permission.AuthorizationReq) (*permission.TokenResp, error) { // 驗證所需 @@ -55,13 +53,23 @@ func (l *NewTokenLogic) NewToken(in *permission.AuthorizationReq) (*permission.T expires := int(in.GetExpires()) refreshExpires := int(in.GetExpires()) if expires <= 0 { - expires = int(l.svcCtx.Config.Token.Expired.Seconds()) + // 將時間加上 300 秒 + sec := time.Duration(l.svcCtx.Config.Token.Expired.Seconds()) * time.Second + newTime := now.Add(sec) + // 獲取 Unix 時間戳 + timestamp := newTime.Unix() + expires = int(timestamp) refreshExpires = expires } // 如果這是一個 Refresh Token 過期時間要比普通的Token 長 if in.GetIsRefreshToken() { - refreshExpires = int(l.svcCtx.Config.Token.RefreshExpires.Seconds()) + // 將時間加上 300 秒 + sec := time.Duration(l.svcCtx.Config.Token.RefreshExpires.Seconds()) * time.Second + newTime := now.Add(sec) + // 獲取 Unix 時間戳 + timestamp := newTime.Unix() + refreshExpires = int(timestamp) } token := entity.Token{ diff --git a/internal/logic/new_token_logic_test.go b/internal/logic/tokenservice/new_token_logic_test.go similarity index 99% rename from internal/logic/new_token_logic_test.go rename to internal/logic/tokenservice/new_token_logic_test.go index e00c816..76263c9 100644 --- a/internal/logic/new_token_logic_test.go +++ b/internal/logic/tokenservice/new_token_logic_test.go @@ -1,4 +1,4 @@ -package logic +package tokenservicelogic import ( "ark-permission/internal/entity" diff --git a/internal/logic/tokenservice/refresh_token_logic.go b/internal/logic/tokenservice/refresh_token_logic.go new file mode 100644 index 0000000..dee0484 --- /dev/null +++ b/internal/logic/tokenservice/refresh_token_logic.go @@ -0,0 +1,133 @@ +package tokenservicelogic + +import ( + "ark-permission/internal/domain" + "ark-permission/internal/entity" + ers "code.30cm.net/wanderland/library-go/errors" + "context" + "time" + + "ark-permission/gen_result/pb/permission" + "ark-permission/internal/svc" + + "github.com/zeromicro/go-zero/core/logx" +) + +type RefreshTokenLogic struct { + ctx context.Context + svcCtx *svc.ServiceContext + logx.Logger +} + +func NewRefreshTokenLogic(ctx context.Context, svcCtx *svc.ServiceContext) *RefreshTokenLogic { + return &RefreshTokenLogic{ + ctx: ctx, + svcCtx: svcCtx, + Logger: logx.WithContext(ctx), + } +} + +type refreshReq struct { + RefreshToken string `json:"grant_type" validate:"required"` + DeviceID string `json:"device_id" validate:"required"` + Scope string `json:"scope" validate:"required"` + Expires int64 `json:"expires" validate:"required"` +} + +// RefreshToken 更新目前的token 以及裡面包含的一次性 Token +func (l *RefreshTokenLogic) RefreshToken(in *permission.RefreshTokenReq) (*permission.RefreshTokenResp, error) { + // 驗證所需 + if err := l.svcCtx.Validate.ValidateAll(&refreshReq{ + RefreshToken: in.GetToken(), + Scope: in.GetScope(), + DeviceID: in.GetDeviceId(), + Expires: in.GetExpires(), + }); err != nil { + return nil, ers.InvalidFormat(err.Error()) + } + // step 1 拿看看有沒有這個 refresh token + token, err := l.svcCtx.TokenRedisRepo.GetByRefresh(l.ctx, in.Token) + if err != nil { + logx.WithCallerSkip(1).WithFields( + logx.Field("func", "TokenRedisRepo.GetByRefresh"), + logx.Field("req", in), + ).Error(err.Error()) + return nil, err + } + // 拿到之後替換掉時間以及 refresh token + // refreshToken 建立 + now := time.Now().UTC() + sec := time.Duration(l.svcCtx.Config.Token.RefreshExpires.Seconds()) * time.Second + newTime := now.Add(sec) + // 獲取 Unix 時間戳 + timestamp := newTime.Unix() + refreshExpires := int(timestamp) + expires := int(in.GetExpires()) + if expires <= 0 { + // 將時間加上 300 秒 + sec := time.Duration(l.svcCtx.Config.Token.Expired.Seconds()) * time.Second + newTime := now.Add(sec) + // 獲取 Unix 時間戳 + timestamp := newTime.Unix() + expires = int(timestamp) + } + + newToken := entity.Token{ + ID: token.ID, + UID: token.UID, + DeviceID: in.GetDeviceId(), + ExpiresIn: expires, + RefreshExpiresIn: refreshExpires, + AccessCreateAt: now, + RefreshCreateAt: now, + } + + claims := claims(map[string]string{ + "uid": token.UID, + }) + claims.SetRole(domain.DefaultRole) + claims.SetID(token.ID) + claims.SetScope(in.GetScope()) + claims.UID() + + if in.GetDeviceId() != "" { + claims.SetDeviceID(in.GetDeviceId()) + } + + newToken.AccessToken, err = generateAccessTokenFunc(newToken, claims, l.svcCtx.Config.Token.Secret) + if err != nil { + logx.WithCallerSkip(1).WithFields( + logx.Field("func", "generateAccessTokenFunc"), + logx.Field("claims", claims), + ).Error(err.Error()) + return nil, err + } + + newToken.RefreshToken = generateRefreshTokenFunc(newToken.AccessToken) + + // 刪除掉舊的 token + err = l.svcCtx.TokenRedisRepo.Delete(l.ctx, token) + if err != nil { + logx.WithCallerSkip(1).WithFields( + logx.Field("func", "TokenRedisRepo.Delete"), + logx.Field("req", token), + ).Error(err.Error()) + return nil, err + } + + err = l.svcCtx.TokenRedisRepo.Create(l.ctx, newToken) + if err != nil { + logx.WithCallerSkip(1).WithFields( + logx.Field("func", "TokenRedisRepo.Create"), + logx.Field("token", token), + ).Error(err.Error()) + return nil, err + } + + return &permission.RefreshTokenResp{ + Token: newToken.AccessToken, + OneTimeToken: newToken.RefreshToken, + ExpiresIn: int64(expires), + TokenType: domain.TokenTypeBearer, + }, nil +} diff --git a/internal/logic/utils_claims.go b/internal/logic/tokenservice/utils_claims.go similarity index 96% rename from internal/logic/utils_claims.go rename to internal/logic/tokenservice/utils_claims.go index 937953d..2d59b66 100644 --- a/internal/logic/utils_claims.go +++ b/internal/logic/tokenservice/utils_claims.go @@ -1,4 +1,4 @@ -package logic +package tokenservicelogic type claims map[string]string diff --git a/internal/logic/utils_jwt.go b/internal/logic/tokenservice/utils_jwt.go similarity index 88% rename from internal/logic/utils_jwt.go rename to internal/logic/tokenservice/utils_jwt.go index f34d9ab..c6550b9 100644 --- a/internal/logic/utils_jwt.go +++ b/internal/logic/tokenservice/utils_jwt.go @@ -1,4 +1,4 @@ -package logic +package tokenservicelogic import ( "ark-permission/internal/domain" @@ -12,6 +12,9 @@ import ( "time" ) +var generateAccessTokenFunc = generateAccessToken +var generateRefreshTokenFunc = generateRefreshToken + func generateAccessToken(token entity.Token, data any, sign string) (string, error) { claim := entity.Claims{ Data: data, @@ -40,7 +43,7 @@ func generateRefreshToken(accessToken string) string { } func parseClaims(ctx context.Context, accessToken string, secret string) (claims, error) { - claimMap, err := parseToken(ctx, accessToken, secret) + claimMap, err := parseToken(accessToken, secret) if err != nil { return claims{}, err } @@ -54,7 +57,7 @@ func parseClaims(ctx context.Context, accessToken string, secret string) (claims return nil, domain.TokenClaimError("get data from claim map error") } -func parseToken(ctx context.Context, accessToken string, secret string) (jwt.MapClaims, error) { +func parseToken(accessToken string, secret string) (jwt.MapClaims, error) { token, err := jwt.Parse(accessToken, func(token *jwt.Token) (any, error) { if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok { return nil, domain.TokenUnexpectedSigningErr(fmt.Sprintf("token unexpected signing method: %v", token.Header["alg"])) diff --git a/internal/logic/tokenservice/validation_token_logic.go b/internal/logic/tokenservice/validation_token_logic.go new file mode 100644 index 0000000..fd00492 --- /dev/null +++ b/internal/logic/tokenservice/validation_token_logic.go @@ -0,0 +1,71 @@ +package tokenservicelogic + +import ( + ers "code.30cm.net/wanderland/library-go/errors" + "context" + + "ark-permission/gen_result/pb/permission" + "ark-permission/internal/svc" + + "github.com/zeromicro/go-zero/core/logx" +) + +type ValidationTokenLogic struct { + ctx context.Context + svcCtx *svc.ServiceContext + logx.Logger +} + +func NewValidationTokenLogic(ctx context.Context, svcCtx *svc.ServiceContext) *ValidationTokenLogic { + return &ValidationTokenLogic{ + ctx: ctx, + svcCtx: svcCtx, + Logger: logx.WithContext(ctx), + } +} + +type refreshTokenReq struct { + Token string `json:"token" validate:"required"` +} + +// ValidationToken 驗證這個 Token 有沒有效 +func (l *ValidationTokenLogic) ValidationToken(in *permission.ValidationTokenReq) (*permission.ValidationTokenResp, error) { + // 驗證所需 + if err := l.svcCtx.Validate.ValidateAll(&refreshTokenReq{ + Token: in.GetToken(), + }); err != nil { + return nil, ers.InvalidFormat(err.Error()) + } + + claims, err := parseClaims(l.ctx, in.GetToken(), l.svcCtx.Config.Token.Secret) + if err != nil { + logx.WithCallerSkip(1).WithFields( + logx.Field("func", "parseClaims"), + ).Error(err.Error()) + return nil, err + } + + token, err := l.svcCtx.TokenRedisRepo.GetByAccess(l.ctx, claims.ID()) + if err != nil { + logx.WithCallerSkip(1).WithFields( + logx.Field("func", "TokenRedisRepo.GetByAccess"), + logx.Field("claims", claims), + ).Error(err.Error()) + return nil, err + } + + return &permission.ValidationTokenResp{ + Token: &permission.Token{ + Id: token.ID, + Uid: token.UID, + DeviceId: token.DeviceID, + AccessCreateAt: token.AccessCreateAt.Unix(), + AccessToken: token.AccessToken, + ExpiresIn: int32(token.ExpiresIn), + RefreshToken: token.RefreshToken, + RefreshExpiresIn: int32(token.RefreshExpiresIn), + RefreshCreateAt: token.RefreshCreateAt.Unix(), + }, + Data: claims, + }, nil +} diff --git a/internal/logic/validation_token_logic.go b/internal/logic/validation_token_logic.go deleted file mode 100644 index 40588d8..0000000 --- a/internal/logic/validation_token_logic.go +++ /dev/null @@ -1,31 +0,0 @@ -package logic - -import ( - "context" - - "ark-permission/gen_result/pb/permission" - "ark-permission/internal/svc" - - "github.com/zeromicro/go-zero/core/logx" -) - -type ValidationTokenLogic struct { - ctx context.Context - svcCtx *svc.ServiceContext - logx.Logger -} - -func NewValidationTokenLogic(ctx context.Context, svcCtx *svc.ServiceContext) *ValidationTokenLogic { - return &ValidationTokenLogic{ - ctx: ctx, - svcCtx: svcCtx, - Logger: logx.WithContext(ctx), - } -} - -// ValidationToken 驗證這個 Token 有沒有效 -func (l *ValidationTokenLogic) ValidationToken(in *permission.ValidationTokenReq) (*permission.ValidationTokenResp, error) { - // todo: add your logic here and delete this line - - return &permission.ValidationTokenResp{}, nil -} diff --git a/internal/repository/token.go b/internal/repository/token.go index 105d794..e434c3b 100644 --- a/internal/repository/token.go +++ b/internal/repository/token.go @@ -22,6 +22,41 @@ type tokenRepository struct { store *redis.Redis } +func (t *tokenRepository) GetAccessTokenCountByUID(uid string) (int, error) { + // TODO implement me + panic("implement me") +} + +func (t *tokenRepository) GetAccessTokensByDeviceID(ctx context.Context, deviceID string) ([]entity.Token, error) { + // TODO implement me + panic("implement me") +} + +func (t *tokenRepository) GetAccessTokenCountByDeviceID(deviceID string) (int, error) { + // TODO implement me + panic("implement me") +} + +func (t *tokenRepository) DeleteAccessTokenByID(ctx context.Context, id string) error { + // TODO implement me + panic("implement me") +} + +func (t *tokenRepository) DeleteAccessTokensByUID(ctx context.Context, uid string) error { + // TODO implement me + panic("implement me") +} + +func (t *tokenRepository) DeleteAccessTokensByDeviceID(ctx context.Context, deviceID string) error { + // TODO implement me + panic("implement me") +} + +func (t *tokenRepository) DeleteAccessTokenByDeviceIDAndUID(ctx context.Context, deviceID, uid string) error { + // TODO implement me + panic("implement me") +} + func NewTokenRepository(param TokenRepositoryParam) repository.TokenRepository { return &tokenRepository{ store: param.Store, @@ -62,10 +97,185 @@ func (t *tokenRepository) Create(ctx context.Context, token entity.Token) error return nil } -func (t *tokenRepository) GetByAccess(_ context.Context, id string) (entity.Token, error) { +// // GetAccessTokensByDeviceID 透過 Device ID 得到目前未過期的token +// func (t *tokenRepository) GetAccessTokensByDeviceID(ctx context.Context, uid string) ([]repository.DeviceToken, error) { +// data, err := t.store.Hgetall(domain.DeviceTokenRedisKey.With(uid).ToString()) +// if err != nil { +// if errors.Is(err, redis.Nil) { +// return nil, nil +// } +// +// return nil, domain.RedisError(fmt.Sprintf("tokenRepository.GetAccessTokensByDeviceID store.HGetAll Device Token error: %v", err.Error())) +// } +// +// ids := make([]repository.DeviceToken, 0, len(data)) +// for deviceID, id := range data { +// ids = append(ids, repository.DeviceToken{ +// DeviceID: deviceID, +// +// // e0a4f824-41db-4eb2-8e5a-d96966ea1d56-1698083859 +// // -11是因為id組成最後11位數是-跟時間戳記 +// TokenID: id[:len(id)-11], +// }) +// } +// return ids, nil +// } + +// GetAccessTokensByUID 透過 uid 得到目前未過期的 token +func (t *tokenRepository) GetAccessTokensByUID(ctx context.Context, uid string) ([]entity.Token, error) { + utKeys, err := t.store.Get(domain.GetUIDTokenRedisKey(uid)) + if err != nil { + // 沒有就視為回空 + if errors.Is(err, redis.Nil) { + return nil, nil + } + + return nil, domain.RedisError(fmt.Sprintf("tokenRepository.GetAccessTokensByUID store.Get GetUIDTokenRedisKey error: %v", err.Error())) + } + + uidTokens := make(entity.UIDToken) + err = json.Unmarshal([]byte(utKeys), &uidTokens) + if err != nil { + return nil, ers.ArkInternal(fmt.Sprintf("tokenRepository.GetAccessTokensByUID json.Unmarshal GetUIDTokenRedisKey error: %v", err)) + } + + now := time.Now().Unix() + var tokens []entity.Token + var deleteToken []string + for id, token := range uidTokens { + if token < now { + deleteToken = append(deleteToken, id) + + continue + } + + tk, err := t.store.Get(domain.GetAccessTokenRedisKey(id)) + if err == nil { + item := entity.Token{} + err = json.Unmarshal([]byte(tk), &item) + if err != nil { + return nil, ers.ArkInternal(fmt.Sprintf("tokenRepository.GetAccessTokensByUID json.Unmarshal GetUIDTokenRedisKey error: %v", err)) + } + tokens = append(tokens, item) + } + + if errors.Is(err, redis.Nil) { + deleteToken = append(deleteToken, id) + } + } + + if len(deleteToken) > 0 { + // 如果失敗也沒關係,其他get method撈取時會在判斷是否過期或存在 + _ = t.DeleteUIDToken(ctx, uid, deleteToken) + } + + return tokens, nil +} + +func (t *tokenRepository) DeleteUIDToken(ctx context.Context, uid string, ids []string) error { + uidTokens := make(entity.UIDToken) + tokenKeys, err := t.store.Get(domain.GetUIDTokenRedisKey(uid)) + if err != nil { + if !errors.Is(err, redis.Nil) { + return fmt.Errorf("tx.get GetDeviceTokenRedisKey error: %w", err) + } + } + + if tokenKeys != "" { + err = json.Unmarshal([]byte(tokenKeys), &uidTokens) + if err != nil { + return fmt.Errorf("json.Unmarshal GetDeviceTokenRedisKey error: %w", err) + } + } + + now := time.Now().Unix() + for k, t := range uidTokens { + // 到期就刪除 + if t < now { + delete(uidTokens, k) + } + } + + for _, id := range ids { + delete(uidTokens, id) + } + + b, err := json.Marshal(uidTokens) + if err != nil { + return fmt.Errorf("json.Marshal UIDToken error: %w", err) + } + + _, err = t.store.SetnxEx(domain.GetUIDTokenRedisKey(uid), string(b), 86400*30) + if err != nil { + return fmt.Errorf("tx.set GetUIDTokenRedisKey error: %w", err) + } + + return nil +} + +func (t *tokenRepository) GetAccessTokenByID(_ context.Context, id string) (entity.Token, error) { return t.get(domain.GetAccessTokenRedisKey(id)) } +func (t *tokenRepository) GetByRefresh(ctx context.Context, refreshToken string) (entity.Token, error) { + id, err := t.store.Get(domain.RefreshTokenRedisKey.With(refreshToken).ToString()) + if err != nil { + return entity.Token{}, err + } + + if errors.Is(err, redis.Nil) || id == "" { + return entity.Token{}, ers.ResourceNotFound("token key not found in redis", domain.RefreshTokenRedisKey.With(refreshToken).ToString()) + } + + if err != nil { + return entity.Token{}, ers.ArkInternal(fmt.Sprintf("store.GetByRefresh refresh token error: %v", err)) + } + + return t.GetAccessTokenByID(ctx, id) +} + +func (t *tokenRepository) DeleteOneTimeToken(ctx context.Context, ids []string, tokens []entity.Token) error { + err := t.store.Pipelined(func(tx redis.Pipeliner) error { + keys := make([]string, 0, len(ids)+len(tokens)) + + for _, id := range ids { + keys = append(keys, domain.RefreshTokenRedisKey.With(id).ToString()) + } + + for _, token := range tokens { + keys = append(keys, domain.RefreshTokenRedisKey.With(token.RefreshToken).ToString()) + } + + for _, key := range keys { + if err := tx.Del(ctx, key).Err(); err != nil { + return domain.RedisDelError(fmt.Sprintf("store.Del key error: %v", err)) + } + } + + return nil + }) + + if err != nil { + return domain.RedisPipLineError(fmt.Sprintf("store.Pipelined error: %v", err)) + } + + return nil +} + +func (t *tokenRepository) CreateOneTimeToken(ctx context.Context, key string, ticket entity.Ticket, expires time.Duration) error { + body, err := json.Marshal(ticket) + if err != nil { + return ers.InvalidFormat("CreateOneTimeToken json.Marshal error:", err.Error()) + } + + _, err = t.store.SetnxEx(domain.GetTicketRedisKey(key), string(body), int(expires.Seconds())) + if err != nil { + return ers.DBError("CreateOneTimeToken store.set error:", err.Error()) + } + + return nil +} + func (t *tokenRepository) Delete(ctx context.Context, token entity.Token) error { err := t.store.Pipelined(func(tx redis.Pipeliner) error { keys := []string{ diff --git a/internal/server/permissionservice/permission_service_server.go b/internal/server/permissionservice/permission_service_server.go new file mode 100644 index 0000000..d645530 --- /dev/null +++ b/internal/server/permissionservice/permission_service_server.go @@ -0,0 +1,20 @@ +// Code generated by goctl. DO NOT EDIT. +// Source: permission.proto + +package server + +import ( + "ark-permission/gen_result/pb/permission" + "ark-permission/internal/svc" +) + +type PermissionServiceServer struct { + svcCtx *svc.ServiceContext + permission.UnimplementedPermissionServiceServer +} + +func NewPermissionServiceServer(svcCtx *svc.ServiceContext) *PermissionServiceServer { + return &PermissionServiceServer{ + svcCtx: svcCtx, + } +} diff --git a/internal/server/roleservice/role_service_server.go b/internal/server/roleservice/role_service_server.go new file mode 100644 index 0000000..0175dc7 --- /dev/null +++ b/internal/server/roleservice/role_service_server.go @@ -0,0 +1,28 @@ +// Code generated by goctl. DO NOT EDIT. +// Source: permission.proto + +package server + +import ( + "context" + + "ark-permission/gen_result/pb/permission" + "ark-permission/internal/logic/roleservice" + "ark-permission/internal/svc" +) + +type RoleServiceServer struct { + svcCtx *svc.ServiceContext + permission.UnimplementedRoleServiceServer +} + +func NewRoleServiceServer(svcCtx *svc.ServiceContext) *RoleServiceServer { + return &RoleServiceServer{ + svcCtx: svcCtx, + } +} + +func (s *RoleServiceServer) Ping(ctx context.Context, in *permission.OKResp) (*permission.OKResp, error) { + l := roleservicelogic.NewPingLogic(ctx, s.svcCtx) + return l.Ping(in) +} diff --git a/internal/server/token_service_server.go b/internal/server/tokenservice/token_service_server.go similarity index 69% rename from internal/server/token_service_server.go rename to internal/server/tokenservice/token_service_server.go index 5e11a23..0187058 100644 --- a/internal/server/token_service_server.go +++ b/internal/server/tokenservice/token_service_server.go @@ -7,7 +7,7 @@ import ( "context" "ark-permission/gen_result/pb/permission" - "ark-permission/internal/logic" + "ark-permission/internal/logic/tokenservice" "ark-permission/internal/svc" ) @@ -24,60 +24,60 @@ func NewTokenServiceServer(svcCtx *svc.ServiceContext) *TokenServiceServer { // NewToken 建立一個新的 Token,例如:AccessToken func (s *TokenServiceServer) NewToken(ctx context.Context, in *permission.AuthorizationReq) (*permission.TokenResp, error) { - l := logic.NewNewTokenLogic(ctx, s.svcCtx) + l := tokenservicelogic.NewNewTokenLogic(ctx, s.svcCtx) return l.NewToken(in) } // RefreshToken 更新目前的token 以及裡面包含的一次性 Token func (s *TokenServiceServer) RefreshToken(ctx context.Context, in *permission.RefreshTokenReq) (*permission.RefreshTokenResp, error) { - l := logic.NewRefreshTokenLogic(ctx, s.svcCtx) + l := tokenservicelogic.NewRefreshTokenLogic(ctx, s.svcCtx) return l.RefreshToken(in) } // CancelToken 取消 Token,也包含他裡面的 One Time Toke func (s *TokenServiceServer) CancelToken(ctx context.Context, in *permission.CancelTokenReq) (*permission.OKResp, error) { - l := logic.NewCancelTokenLogic(ctx, s.svcCtx) + l := tokenservicelogic.NewCancelTokenLogic(ctx, s.svcCtx) return l.CancelToken(in) } -// CancelTokenByUID 取消 Token (取消這個用戶從不同 Device 登入的所有 Token),也包含他裡面的 One Time Toke +// CancelTokenByUid 取消 Token (取消這個用戶從不同 Device 登入的所有 Token),也包含他裡面的 One Time Toke func (s *TokenServiceServer) CancelTokenByUid(ctx context.Context, in *permission.DoTokenByUIDReq) (*permission.OKResp, error) { - l := logic.NewCancelTokenByUidLogic(ctx, s.svcCtx) + l := tokenservicelogic.NewCancelTokenByUidLogic(ctx, s.svcCtx) return l.CancelTokenByUid(in) } -// CancelTokenByDeviceID 取消 Token +// CancelTokenByDeviceId 取消 Token func (s *TokenServiceServer) CancelTokenByDeviceId(ctx context.Context, in *permission.DoTokenByDeviceIDReq) (*permission.OKResp, error) { - l := logic.NewCancelTokenByDeviceIdLogic(ctx, s.svcCtx) + l := tokenservicelogic.NewCancelTokenByDeviceIdLogic(ctx, s.svcCtx) return l.CancelTokenByDeviceId(in) } // ValidationToken 驗證這個 Token 有沒有效 func (s *TokenServiceServer) ValidationToken(ctx context.Context, in *permission.ValidationTokenReq) (*permission.ValidationTokenResp, error) { - l := logic.NewValidationTokenLogic(ctx, s.svcCtx) + l := tokenservicelogic.NewValidationTokenLogic(ctx, s.svcCtx) return l.ValidationToken(in) } -// GetUserTokensByDeviceIDs 取得目前所對應的 DeviceID 所存在的 Tokens +// GetUserTokensByDeviceId 取得目前所對應的 DeviceID 所存在的 Tokens func (s *TokenServiceServer) GetUserTokensByDeviceId(ctx context.Context, in *permission.DoTokenByDeviceIDReq) (*permission.Tokens, error) { - l := logic.NewGetUserTokensByDeviceIdLogic(ctx, s.svcCtx) + l := tokenservicelogic.NewGetUserTokensByDeviceIdLogic(ctx, s.svcCtx) return l.GetUserTokensByDeviceId(in) } -// GetUserTokensByUID 取得目前所對應的 UID 所存在的 Tokens -func (s *TokenServiceServer) GetUserTokensByUid(ctx context.Context, in *permission.DoTokenByUIDReq) (*permission.Tokens, error) { - l := logic.NewGetUserTokensByUidLogic(ctx, s.svcCtx) +// GetUserTokensByUid 取得目前所對應的 UID 所存在的 Tokens +func (s *TokenServiceServer) GetUserTokensByUid(ctx context.Context, in *permission.QueryTokenByUIDReq) (*permission.Tokens, error) { + l := tokenservicelogic.NewGetUserTokensByUidLogic(ctx, s.svcCtx) return l.GetUserTokensByUid(in) } // NewOneTimeToken 建立一次性使用,例如:RefreshToken func (s *TokenServiceServer) NewOneTimeToken(ctx context.Context, in *permission.CreateOneTimeTokenReq) (*permission.CreateOneTimeTokenResp, error) { - l := logic.NewNewOneTimeTokenLogic(ctx, s.svcCtx) + l := tokenservicelogic.NewNewOneTimeTokenLogic(ctx, s.svcCtx) return l.NewOneTimeToken(in) } // CancelOneTimeToken 取消一次性使用 -func (s *TokenServiceServer) CancelOneTimeToken(ctx context.Context, in *permission.CreateOneTimeTokenReq) (*permission.CreateOneTimeTokenResp, error) { - l := logic.NewCancelOneTimeTokenLogic(ctx, s.svcCtx) +func (s *TokenServiceServer) CancelOneTimeToken(ctx context.Context, in *permission.CancelOneTimeTokenReq) (*permission.OKResp, error) { + l := tokenservicelogic.NewCancelOneTimeTokenLogic(ctx, s.svcCtx) return l.CancelOneTimeToken(in) } diff --git a/permission.go b/permission.go index 89037e5..9fe85f9 100644 --- a/permission.go +++ b/permission.go @@ -1,12 +1,14 @@ package main import ( + permissionservice "ark-permission/internal/server/permissionservice" + roleservice "ark-permission/internal/server/roleservice" + tokenservice "ark-permission/internal/server/tokenservice" "flag" "fmt" "ark-permission/gen_result/pb/permission" "ark-permission/internal/config" - "ark-permission/internal/server" "ark-permission/internal/svc" "github.com/zeromicro/go-zero/core/conf" @@ -26,7 +28,9 @@ func main() { ctx := svc.NewServiceContext(c) s := zrpc.MustNewServer(c.RpcServerConf, func(grpcServer *grpc.Server) { - permission.RegisterTokenServiceServer(grpcServer, server.NewTokenServiceServer(ctx)) + permission.RegisterTokenServiceServer(grpcServer, tokenservice.NewTokenServiceServer(ctx)) + permission.RegisterRoleServiceServer(grpcServer, roleservice.NewRoleServiceServer(ctx)) + permission.RegisterPermissionServiceServer(grpcServer, permissionservice.NewPermissionServiceServer(ctx)) if c.Mode == service.DevMode || c.Mode == service.TestMode { reflection.Register(grpcServer) diff --git a/tokenservice/token_service.go b/tokenservice/token_service.go index 28be4ee..d8ee889 100644 --- a/tokenservice/token_service.go +++ b/tokenservice/token_service.go @@ -20,6 +20,7 @@ type ( DoTokenByDeviceIDReq = permission.DoTokenByDeviceIDReq DoTokenByUIDReq = permission.DoTokenByUIDReq OKResp = permission.OKResp + QueryTokenByUIDReq = permission.QueryTokenByUIDReq RefreshTokenReq = permission.RefreshTokenReq RefreshTokenResp = permission.RefreshTokenResp Token = permission.Token @@ -35,16 +36,16 @@ type ( RefreshToken(ctx context.Context, in *RefreshTokenReq, opts ...grpc.CallOption) (*RefreshTokenResp, error) // CancelToken 取消 Token,也包含他裡面的 One Time Toke CancelToken(ctx context.Context, in *CancelTokenReq, opts ...grpc.CallOption) (*OKResp, error) - // CancelTokenByUID 取消 Token (取消這個用戶從不同 Device 登入的所有 Token),也包含他裡面的 One Time Toke + // CancelTokenByUid 取消 Token (取消這個用戶從不同 Device 登入的所有 Token),也包含他裡面的 One Time Toke CancelTokenByUid(ctx context.Context, in *DoTokenByUIDReq, opts ...grpc.CallOption) (*OKResp, error) - // CancelTokenByDeviceID 取消 Token + // CancelTokenByDeviceId 取消 Token CancelTokenByDeviceId(ctx context.Context, in *DoTokenByDeviceIDReq, opts ...grpc.CallOption) (*OKResp, error) // ValidationToken 驗證這個 Token 有沒有效 ValidationToken(ctx context.Context, in *ValidationTokenReq, opts ...grpc.CallOption) (*ValidationTokenResp, error) - // GetUserTokensByDeviceIDs 取得目前所對應的 DeviceID 所存在的 Tokens + // GetUserTokensByDeviceId 取得目前所對應的 DeviceID 所存在的 Tokens GetUserTokensByDeviceId(ctx context.Context, in *DoTokenByDeviceIDReq, opts ...grpc.CallOption) (*Tokens, error) - // GetUserTokensByUID 取得目前所對應的 UID 所存在的 Tokens - GetUserTokensByUid(ctx context.Context, in *DoTokenByUIDReq, opts ...grpc.CallOption) (*Tokens, error) + // GetUserTokensByUid 取得目前所對應的 UID 所存在的 Tokens + GetUserTokensByUid(ctx context.Context, in *QueryTokenByUIDReq, opts ...grpc.CallOption) (*Tokens, error) // NewOneTimeToken 建立一次性使用,例如:RefreshToken NewOneTimeToken(ctx context.Context, in *CreateOneTimeTokenReq, opts ...grpc.CallOption) (*CreateOneTimeTokenResp, error) // CancelOneTimeToken 取消一次性使用 @@ -80,13 +81,13 @@ func (m *defaultTokenService) CancelToken(ctx context.Context, in *CancelTokenRe return client.CancelToken(ctx, in, opts...) } -// CancelTokenByUID 取消 Token (取消這個用戶從不同 Device 登入的所有 Token),也包含他裡面的 One Time Toke +// CancelTokenByUid 取消 Token (取消這個用戶從不同 Device 登入的所有 Token),也包含他裡面的 One Time Toke func (m *defaultTokenService) CancelTokenByUid(ctx context.Context, in *DoTokenByUIDReq, opts ...grpc.CallOption) (*OKResp, error) { client := permission.NewTokenServiceClient(m.cli.Conn()) return client.CancelTokenByUid(ctx, in, opts...) } -// CancelTokenByDeviceID 取消 Token +// CancelTokenByDeviceId 取消 Token func (m *defaultTokenService) CancelTokenByDeviceId(ctx context.Context, in *DoTokenByDeviceIDReq, opts ...grpc.CallOption) (*OKResp, error) { client := permission.NewTokenServiceClient(m.cli.Conn()) return client.CancelTokenByDeviceId(ctx, in, opts...) @@ -98,14 +99,14 @@ func (m *defaultTokenService) ValidationToken(ctx context.Context, in *Validatio return client.ValidationToken(ctx, in, opts...) } -// GetUserTokensByDeviceIDs 取得目前所對應的 DeviceID 所存在的 Tokens +// GetUserTokensByDeviceId 取得目前所對應的 DeviceID 所存在的 Tokens func (m *defaultTokenService) GetUserTokensByDeviceId(ctx context.Context, in *DoTokenByDeviceIDReq, opts ...grpc.CallOption) (*Tokens, error) { client := permission.NewTokenServiceClient(m.cli.Conn()) return client.GetUserTokensByDeviceId(ctx, in, opts...) } -// GetUserTokensByUID 取得目前所對應的 UID 所存在的 Tokens -func (m *defaultTokenService) GetUserTokensByUid(ctx context.Context, in *DoTokenByUIDReq, opts ...grpc.CallOption) (*Tokens, error) { +// GetUserTokensByUid 取得目前所對應的 UID 所存在的 Tokens +func (m *defaultTokenService) GetUserTokensByUid(ctx context.Context, in *QueryTokenByUIDReq, opts ...grpc.CallOption) (*Tokens, error) { client := permission.NewTokenServiceClient(m.cli.Conn()) return client.GetUserTokensByUid(ctx, in, opts...) }