From 9717929231de2e3bcaafc7e4839548165dbb81fa Mon Sep 17 00:00:00 2001 From: zhx Date: Tue, 30 Jun 2026 10:47:21 +0800 Subject: [PATCH] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E7=99=BD=E5=90=8D=E5=8D=95us?= =?UTF-8?q?erid?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- api/proto/user/v1/auth.pb.go | 624 +++++++++++------- api/proto/user/v1/auth.proto | 14 + api/proto/user/v1/auth_grpc.pb.go | 64 +- .../mysql/051_login_risk_user_whitelist.sql | 14 + server/admin/cmd/server/main.go | 2 +- .../internal/integration/userclient/client.go | 28 + .../internal/modules/regionblock/handler.go | 18 +- .../internal/modules/regionblock/service.go | 143 +++- .../modules/regionblock/service_test.go | 59 ++ .../deploy/mysql/initdb/001_user_service.sql | 15 + .../user-service/internal/domain/auth/risk.go | 11 + .../internal/service/auth/risk_cache.go | 27 + .../internal/service/auth/risk_policy.go | 137 +++- .../internal/service/auth/risk_worker.go | 13 + .../internal/service/auth/service.go | 4 + .../internal/service/auth/service_test.go | 100 ++- .../internal/storage/mysql/auth/risk.go | 29 + .../internal/testutil/mysqltest/mysqltest.go | 5 + .../internal/transport/grpc/server.go | 15 + 19 files changed, 1047 insertions(+), 275 deletions(-) create mode 100644 scripts/mysql/051_login_risk_user_whitelist.sql create mode 100644 server/admin/internal/modules/regionblock/service_test.go diff --git a/api/proto/user/v1/auth.pb.go b/api/proto/user/v1/auth.pb.go index c7aba5d5..07457887 100644 --- a/api/proto/user/v1/auth.pb.go +++ b/api/proto/user/v1/auth.pb.go @@ -1448,6 +1448,122 @@ func (x *CheckLoginRiskIPWhitelistResponse) GetWhitelisted() bool { return false } +// RefreshLoginRiskWhitelistCacheRequest 要求 user-service 从 MySQL 重建登录风控白名单缓存。 +type RefreshLoginRiskWhitelistCacheRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Meta *RequestMeta `protobuf:"bytes,1,opt,name=meta,proto3" json:"meta,omitempty"` +} + +func (x *RefreshLoginRiskWhitelistCacheRequest) Reset() { + *x = RefreshLoginRiskWhitelistCacheRequest{} + mi := &file_proto_user_v1_auth_proto_msgTypes[18] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *RefreshLoginRiskWhitelistCacheRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*RefreshLoginRiskWhitelistCacheRequest) ProtoMessage() {} + +func (x *RefreshLoginRiskWhitelistCacheRequest) ProtoReflect() protoreflect.Message { + mi := &file_proto_user_v1_auth_proto_msgTypes[18] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use RefreshLoginRiskWhitelistCacheRequest.ProtoReflect.Descriptor instead. +func (*RefreshLoginRiskWhitelistCacheRequest) Descriptor() ([]byte, []int) { + return file_proto_user_v1_auth_proto_rawDescGZIP(), []int{18} +} + +func (x *RefreshLoginRiskWhitelistCacheRequest) GetMeta() *RequestMeta { + if x != nil { + return x.Meta + } + return nil +} + +// RefreshLoginRiskWhitelistCacheResponse 返回本次刷新后的白名单规模,便于后台保存后确认缓存已更新。 +type RefreshLoginRiskWhitelistCacheResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Refreshed bool `protobuf:"varint,1,opt,name=refreshed,proto3" json:"refreshed,omitempty"` + IpWhitelistCount int32 `protobuf:"varint,2,opt,name=ip_whitelist_count,json=ipWhitelistCount,proto3" json:"ip_whitelist_count,omitempty"` + UserWhitelistCount int32 `protobuf:"varint,3,opt,name=user_whitelist_count,json=userWhitelistCount,proto3" json:"user_whitelist_count,omitempty"` + UserCacheExpiresAtMs int64 `protobuf:"varint,4,opt,name=user_cache_expires_at_ms,json=userCacheExpiresAtMs,proto3" json:"user_cache_expires_at_ms,omitempty"` +} + +func (x *RefreshLoginRiskWhitelistCacheResponse) Reset() { + *x = RefreshLoginRiskWhitelistCacheResponse{} + mi := &file_proto_user_v1_auth_proto_msgTypes[19] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *RefreshLoginRiskWhitelistCacheResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*RefreshLoginRiskWhitelistCacheResponse) ProtoMessage() {} + +func (x *RefreshLoginRiskWhitelistCacheResponse) ProtoReflect() protoreflect.Message { + mi := &file_proto_user_v1_auth_proto_msgTypes[19] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use RefreshLoginRiskWhitelistCacheResponse.ProtoReflect.Descriptor instead. +func (*RefreshLoginRiskWhitelistCacheResponse) Descriptor() ([]byte, []int) { + return file_proto_user_v1_auth_proto_rawDescGZIP(), []int{19} +} + +func (x *RefreshLoginRiskWhitelistCacheResponse) GetRefreshed() bool { + if x != nil { + return x.Refreshed + } + return false +} + +func (x *RefreshLoginRiskWhitelistCacheResponse) GetIpWhitelistCount() int32 { + if x != nil { + return x.IpWhitelistCount + } + return 0 +} + +func (x *RefreshLoginRiskWhitelistCacheResponse) GetUserWhitelistCount() int32 { + if x != nil { + return x.UserWhitelistCount + } + return 0 +} + +func (x *RefreshLoginRiskWhitelistCacheResponse) GetUserCacheExpiresAtMs() int64 { + if x != nil { + return x.UserCacheExpiresAtMs + } + return 0 +} + // RegisterRiskConfig 是注册链路读取的风控配置。 type RegisterRiskConfig struct { state protoimpl.MessageState @@ -1462,7 +1578,7 @@ type RegisterRiskConfig struct { func (x *RegisterRiskConfig) Reset() { *x = RegisterRiskConfig{} - mi := &file_proto_user_v1_auth_proto_msgTypes[18] + mi := &file_proto_user_v1_auth_proto_msgTypes[20] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1474,7 +1590,7 @@ func (x *RegisterRiskConfig) String() string { func (*RegisterRiskConfig) ProtoMessage() {} func (x *RegisterRiskConfig) ProtoReflect() protoreflect.Message { - mi := &file_proto_user_v1_auth_proto_msgTypes[18] + mi := &file_proto_user_v1_auth_proto_msgTypes[20] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1487,7 +1603,7 @@ func (x *RegisterRiskConfig) ProtoReflect() protoreflect.Message { // Deprecated: Use RegisterRiskConfig.ProtoReflect.Descriptor instead. func (*RegisterRiskConfig) Descriptor() ([]byte, []int) { - return file_proto_user_v1_auth_proto_rawDescGZIP(), []int{18} + return file_proto_user_v1_auth_proto_rawDescGZIP(), []int{20} } func (x *RegisterRiskConfig) GetAppCode() string { @@ -1529,7 +1645,7 @@ type GetRegisterRiskConfigRequest struct { func (x *GetRegisterRiskConfigRequest) Reset() { *x = GetRegisterRiskConfigRequest{} - mi := &file_proto_user_v1_auth_proto_msgTypes[19] + mi := &file_proto_user_v1_auth_proto_msgTypes[21] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1541,7 +1657,7 @@ func (x *GetRegisterRiskConfigRequest) String() string { func (*GetRegisterRiskConfigRequest) ProtoMessage() {} func (x *GetRegisterRiskConfigRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_user_v1_auth_proto_msgTypes[19] + mi := &file_proto_user_v1_auth_proto_msgTypes[21] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1554,7 +1670,7 @@ func (x *GetRegisterRiskConfigRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use GetRegisterRiskConfigRequest.ProtoReflect.Descriptor instead. func (*GetRegisterRiskConfigRequest) Descriptor() ([]byte, []int) { - return file_proto_user_v1_auth_proto_rawDescGZIP(), []int{19} + return file_proto_user_v1_auth_proto_rawDescGZIP(), []int{21} } func (x *GetRegisterRiskConfigRequest) GetMeta() *RequestMeta { @@ -1574,7 +1690,7 @@ type GetRegisterRiskConfigResponse struct { func (x *GetRegisterRiskConfigResponse) Reset() { *x = GetRegisterRiskConfigResponse{} - mi := &file_proto_user_v1_auth_proto_msgTypes[20] + mi := &file_proto_user_v1_auth_proto_msgTypes[22] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1586,7 +1702,7 @@ func (x *GetRegisterRiskConfigResponse) String() string { func (*GetRegisterRiskConfigResponse) ProtoMessage() {} func (x *GetRegisterRiskConfigResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_user_v1_auth_proto_msgTypes[20] + mi := &file_proto_user_v1_auth_proto_msgTypes[22] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1599,7 +1715,7 @@ func (x *GetRegisterRiskConfigResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use GetRegisterRiskConfigResponse.ProtoReflect.Descriptor instead. func (*GetRegisterRiskConfigResponse) Descriptor() ([]byte, []int) { - return file_proto_user_v1_auth_proto_rawDescGZIP(), []int{20} + return file_proto_user_v1_auth_proto_rawDescGZIP(), []int{22} } func (x *GetRegisterRiskConfigResponse) GetConfig() *RegisterRiskConfig { @@ -1622,7 +1738,7 @@ type UpdateRegisterRiskConfigRequest struct { func (x *UpdateRegisterRiskConfigRequest) Reset() { *x = UpdateRegisterRiskConfigRequest{} - mi := &file_proto_user_v1_auth_proto_msgTypes[21] + mi := &file_proto_user_v1_auth_proto_msgTypes[23] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1634,7 +1750,7 @@ func (x *UpdateRegisterRiskConfigRequest) String() string { func (*UpdateRegisterRiskConfigRequest) ProtoMessage() {} func (x *UpdateRegisterRiskConfigRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_user_v1_auth_proto_msgTypes[21] + mi := &file_proto_user_v1_auth_proto_msgTypes[23] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1647,7 +1763,7 @@ func (x *UpdateRegisterRiskConfigRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use UpdateRegisterRiskConfigRequest.ProtoReflect.Descriptor instead. func (*UpdateRegisterRiskConfigRequest) Descriptor() ([]byte, []int) { - return file_proto_user_v1_auth_proto_rawDescGZIP(), []int{21} + return file_proto_user_v1_auth_proto_rawDescGZIP(), []int{23} } func (x *UpdateRegisterRiskConfigRequest) GetMeta() *RequestMeta { @@ -1681,7 +1797,7 @@ type UpdateRegisterRiskConfigResponse struct { func (x *UpdateRegisterRiskConfigResponse) Reset() { *x = UpdateRegisterRiskConfigResponse{} - mi := &file_proto_user_v1_auth_proto_msgTypes[22] + mi := &file_proto_user_v1_auth_proto_msgTypes[24] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1693,7 +1809,7 @@ func (x *UpdateRegisterRiskConfigResponse) String() string { func (*UpdateRegisterRiskConfigResponse) ProtoMessage() {} func (x *UpdateRegisterRiskConfigResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_user_v1_auth_proto_msgTypes[22] + mi := &file_proto_user_v1_auth_proto_msgTypes[24] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1706,7 +1822,7 @@ func (x *UpdateRegisterRiskConfigResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use UpdateRegisterRiskConfigResponse.ProtoReflect.Descriptor instead. func (*UpdateRegisterRiskConfigResponse) Descriptor() ([]byte, []int) { - return file_proto_user_v1_auth_proto_rawDescGZIP(), []int{22} + return file_proto_user_v1_auth_proto_rawDescGZIP(), []int{24} } func (x *UpdateRegisterRiskConfigResponse) GetConfig() *RegisterRiskConfig { @@ -1729,7 +1845,7 @@ type AppHeartbeatRequest struct { func (x *AppHeartbeatRequest) Reset() { *x = AppHeartbeatRequest{} - mi := &file_proto_user_v1_auth_proto_msgTypes[23] + mi := &file_proto_user_v1_auth_proto_msgTypes[25] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1741,7 +1857,7 @@ func (x *AppHeartbeatRequest) String() string { func (*AppHeartbeatRequest) ProtoMessage() {} func (x *AppHeartbeatRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_user_v1_auth_proto_msgTypes[23] + mi := &file_proto_user_v1_auth_proto_msgTypes[25] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1754,7 +1870,7 @@ func (x *AppHeartbeatRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use AppHeartbeatRequest.ProtoReflect.Descriptor instead. func (*AppHeartbeatRequest) Descriptor() ([]byte, []int) { - return file_proto_user_v1_auth_proto_rawDescGZIP(), []int{23} + return file_proto_user_v1_auth_proto_rawDescGZIP(), []int{25} } func (x *AppHeartbeatRequest) GetMeta() *RequestMeta { @@ -1792,7 +1908,7 @@ type AppHeartbeatResponse struct { func (x *AppHeartbeatResponse) Reset() { *x = AppHeartbeatResponse{} - mi := &file_proto_user_v1_auth_proto_msgTypes[24] + mi := &file_proto_user_v1_auth_proto_msgTypes[26] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1804,7 +1920,7 @@ func (x *AppHeartbeatResponse) String() string { func (*AppHeartbeatResponse) ProtoMessage() {} func (x *AppHeartbeatResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_user_v1_auth_proto_msgTypes[24] + mi := &file_proto_user_v1_auth_proto_msgTypes[26] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1817,7 +1933,7 @@ func (x *AppHeartbeatResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use AppHeartbeatResponse.ProtoReflect.Descriptor instead. func (*AppHeartbeatResponse) Descriptor() ([]byte, []int) { - return file_proto_user_v1_auth_proto_rawDescGZIP(), []int{24} + return file_proto_user_v1_auth_proto_rawDescGZIP(), []int{26} } func (x *AppHeartbeatResponse) GetAccepted() bool { @@ -2073,152 +2189,181 @@ var file_proto_user_v1_auth_proto_rawDesc = []byte{ 0x69, 0x6e, 0x52, 0x69, 0x73, 0x6b, 0x49, 0x50, 0x57, 0x68, 0x69, 0x74, 0x65, 0x6c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x20, 0x0a, 0x0b, 0x77, 0x68, 0x69, 0x74, 0x65, 0x6c, 0x69, 0x73, 0x74, 0x65, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0b, - 0x77, 0x68, 0x69, 0x74, 0x65, 0x6c, 0x69, 0x73, 0x74, 0x65, 0x64, 0x22, 0xb9, 0x01, 0x0a, 0x12, - 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x52, 0x69, 0x73, 0x6b, 0x43, 0x6f, 0x6e, 0x66, - 0x69, 0x67, 0x12, 0x19, 0x0a, 0x08, 0x61, 0x70, 0x70, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x01, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, 0x70, 0x70, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x35, 0x0a, - 0x17, 0x6d, 0x61, 0x78, 0x5f, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x73, 0x5f, 0x70, 0x65, - 0x72, 0x5f, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x14, - 0x6d, 0x61, 0x78, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x73, 0x50, 0x65, 0x72, 0x44, 0x65, - 0x76, 0x69, 0x63, 0x65, 0x12, 0x22, 0x0a, 0x0d, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x5f, - 0x61, 0x74, 0x5f, 0x6d, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x75, 0x70, 0x64, - 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, 0x4d, 0x73, 0x12, 0x2d, 0x0a, 0x13, 0x75, 0x70, 0x64, 0x61, - 0x74, 0x65, 0x64, 0x5f, 0x62, 0x79, 0x5f, 0x61, 0x64, 0x6d, 0x69, 0x6e, 0x5f, 0x69, 0x64, 0x18, - 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x10, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x42, 0x79, - 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x49, 0x64, 0x22, 0x4e, 0x0a, 0x1c, 0x47, 0x65, 0x74, 0x52, 0x65, - 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x52, 0x69, 0x73, 0x6b, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, - 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2e, 0x0a, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x18, - 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, - 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x74, - 0x61, 0x52, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x22, 0x5a, 0x0a, 0x1d, 0x47, 0x65, 0x74, 0x52, 0x65, + 0x77, 0x68, 0x69, 0x74, 0x65, 0x6c, 0x69, 0x73, 0x74, 0x65, 0x64, 0x22, 0x57, 0x0a, 0x25, 0x52, + 0x65, 0x66, 0x72, 0x65, 0x73, 0x68, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x52, 0x69, 0x73, 0x6b, 0x57, + 0x68, 0x69, 0x74, 0x65, 0x6c, 0x69, 0x73, 0x74, 0x43, 0x61, 0x63, 0x68, 0x65, 0x52, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x12, 0x2e, 0x0a, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, + 0x76, 0x31, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x74, 0x61, 0x52, 0x04, + 0x6d, 0x65, 0x74, 0x61, 0x22, 0xde, 0x01, 0x0a, 0x26, 0x52, 0x65, 0x66, 0x72, 0x65, 0x73, 0x68, + 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x52, 0x69, 0x73, 0x6b, 0x57, 0x68, 0x69, 0x74, 0x65, 0x6c, 0x69, + 0x73, 0x74, 0x43, 0x61, 0x63, 0x68, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, + 0x1c, 0x0a, 0x09, 0x72, 0x65, 0x66, 0x72, 0x65, 0x73, 0x68, 0x65, 0x64, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x08, 0x52, 0x09, 0x72, 0x65, 0x66, 0x72, 0x65, 0x73, 0x68, 0x65, 0x64, 0x12, 0x2c, 0x0a, + 0x12, 0x69, 0x70, 0x5f, 0x77, 0x68, 0x69, 0x74, 0x65, 0x6c, 0x69, 0x73, 0x74, 0x5f, 0x63, 0x6f, + 0x75, 0x6e, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x10, 0x69, 0x70, 0x57, 0x68, 0x69, + 0x74, 0x65, 0x6c, 0x69, 0x73, 0x74, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x30, 0x0a, 0x14, 0x75, + 0x73, 0x65, 0x72, 0x5f, 0x77, 0x68, 0x69, 0x74, 0x65, 0x6c, 0x69, 0x73, 0x74, 0x5f, 0x63, 0x6f, + 0x75, 0x6e, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x05, 0x52, 0x12, 0x75, 0x73, 0x65, 0x72, 0x57, + 0x68, 0x69, 0x74, 0x65, 0x6c, 0x69, 0x73, 0x74, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x36, 0x0a, + 0x18, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x63, 0x61, 0x63, 0x68, 0x65, 0x5f, 0x65, 0x78, 0x70, 0x69, + 0x72, 0x65, 0x73, 0x5f, 0x61, 0x74, 0x5f, 0x6d, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, + 0x14, 0x75, 0x73, 0x65, 0x72, 0x43, 0x61, 0x63, 0x68, 0x65, 0x45, 0x78, 0x70, 0x69, 0x72, 0x65, + 0x73, 0x41, 0x74, 0x4d, 0x73, 0x22, 0xb9, 0x01, 0x0a, 0x12, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, + 0x65, 0x72, 0x52, 0x69, 0x73, 0x6b, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x19, 0x0a, 0x08, + 0x61, 0x70, 0x70, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, + 0x61, 0x70, 0x70, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x35, 0x0a, 0x17, 0x6d, 0x61, 0x78, 0x5f, 0x61, + 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x73, 0x5f, 0x70, 0x65, 0x72, 0x5f, 0x64, 0x65, 0x76, 0x69, + 0x63, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x14, 0x6d, 0x61, 0x78, 0x41, 0x63, 0x63, + 0x6f, 0x75, 0x6e, 0x74, 0x73, 0x50, 0x65, 0x72, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x12, 0x22, + 0x0a, 0x0d, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x5f, 0x6d, 0x73, 0x18, + 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, + 0x4d, 0x73, 0x12, 0x2d, 0x0a, 0x13, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x62, 0x79, + 0x5f, 0x61, 0x64, 0x6d, 0x69, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, + 0x10, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x42, 0x79, 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x49, + 0x64, 0x22, 0x4e, 0x0a, 0x1c, 0x47, 0x65, 0x74, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, + 0x52, 0x69, 0x73, 0x6b, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x12, 0x2e, 0x0a, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, + 0x1a, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x74, 0x61, 0x52, 0x04, 0x6d, 0x65, 0x74, + 0x61, 0x22, 0x5a, 0x0a, 0x1d, 0x47, 0x65, 0x74, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, + 0x52, 0x69, 0x73, 0x6b, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x65, 0x12, 0x39, 0x0a, 0x06, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x0b, 0x32, 0x21, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, + 0x76, 0x31, 0x2e, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x52, 0x69, 0x73, 0x6b, 0x43, + 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x06, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x22, 0xb4, 0x01, + 0x0a, 0x1f, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, + 0x52, 0x69, 0x73, 0x6b, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x12, 0x2e, 0x0a, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, + 0x1a, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x74, 0x61, 0x52, 0x04, 0x6d, 0x65, 0x74, + 0x61, 0x12, 0x35, 0x0a, 0x17, 0x6d, 0x61, 0x78, 0x5f, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, + 0x73, 0x5f, 0x70, 0x65, 0x72, 0x5f, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x18, 0x02, 0x20, 0x01, + 0x28, 0x05, 0x52, 0x14, 0x6d, 0x61, 0x78, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x73, 0x50, + 0x65, 0x72, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x12, 0x2a, 0x0a, 0x11, 0x6f, 0x70, 0x65, 0x72, + 0x61, 0x74, 0x6f, 0x72, 0x5f, 0x61, 0x64, 0x6d, 0x69, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, + 0x01, 0x28, 0x03, 0x52, 0x0f, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x6f, 0x72, 0x41, 0x64, 0x6d, + 0x69, 0x6e, 0x49, 0x64, 0x22, 0x5d, 0x0a, 0x20, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x52, 0x69, 0x73, 0x6b, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x39, 0x0a, 0x06, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x21, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x52, 0x69, 0x73, 0x6b, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x06, 0x63, 0x6f, 0x6e, - 0x66, 0x69, 0x67, 0x22, 0xb4, 0x01, 0x0a, 0x1f, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x52, 0x65, - 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x52, 0x69, 0x73, 0x6b, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, - 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2e, 0x0a, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x18, - 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, - 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x74, - 0x61, 0x52, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x12, 0x35, 0x0a, 0x17, 0x6d, 0x61, 0x78, 0x5f, 0x61, - 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x73, 0x5f, 0x70, 0x65, 0x72, 0x5f, 0x64, 0x65, 0x76, 0x69, - 0x63, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x14, 0x6d, 0x61, 0x78, 0x41, 0x63, 0x63, - 0x6f, 0x75, 0x6e, 0x74, 0x73, 0x50, 0x65, 0x72, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x12, 0x2a, - 0x0a, 0x11, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x6f, 0x72, 0x5f, 0x61, 0x64, 0x6d, 0x69, 0x6e, - 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0f, 0x6f, 0x70, 0x65, 0x72, 0x61, - 0x74, 0x6f, 0x72, 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x49, 0x64, 0x22, 0x5d, 0x0a, 0x20, 0x55, 0x70, - 0x64, 0x61, 0x74, 0x65, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x52, 0x69, 0x73, 0x6b, - 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x39, - 0x0a, 0x06, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x21, - 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x52, - 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x52, 0x69, 0x73, 0x6b, 0x43, 0x6f, 0x6e, 0x66, 0x69, - 0x67, 0x52, 0x06, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x22, 0x7d, 0x0a, 0x13, 0x41, 0x70, 0x70, - 0x48, 0x65, 0x61, 0x72, 0x74, 0x62, 0x65, 0x61, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, - 0x12, 0x2e, 0x0a, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, - 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x52, - 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x74, 0x61, 0x52, 0x04, 0x6d, 0x65, 0x74, 0x61, - 0x12, 0x17, 0x0a, 0x07, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, - 0x03, 0x52, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x73, 0x65, 0x73, - 0x73, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x73, - 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x22, 0xb0, 0x01, 0x0a, 0x14, 0x41, 0x70, 0x70, - 0x48, 0x65, 0x61, 0x72, 0x74, 0x62, 0x65, 0x61, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, - 0x65, 0x12, 0x1a, 0x0a, 0x08, 0x61, 0x63, 0x63, 0x65, 0x70, 0x74, 0x65, 0x64, 0x18, 0x01, 0x20, - 0x01, 0x28, 0x08, 0x52, 0x08, 0x61, 0x63, 0x63, 0x65, 0x70, 0x74, 0x65, 0x64, 0x12, 0x26, 0x0a, - 0x0f, 0x68, 0x65, 0x61, 0x72, 0x74, 0x62, 0x65, 0x61, 0x74, 0x5f, 0x61, 0x74, 0x5f, 0x6d, 0x73, - 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0d, 0x68, 0x65, 0x61, 0x72, 0x74, 0x62, 0x65, 0x61, - 0x74, 0x41, 0x74, 0x4d, 0x73, 0x12, 0x24, 0x0a, 0x0e, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x5f, - 0x74, 0x69, 0x6d, 0x65, 0x5f, 0x6d, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x73, - 0x65, 0x72, 0x76, 0x65, 0x72, 0x54, 0x69, 0x6d, 0x65, 0x4d, 0x73, 0x12, 0x2e, 0x0a, 0x05, 0x74, - 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x68, 0x79, 0x61, - 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x75, 0x74, 0x68, 0x54, - 0x6f, 0x6b, 0x65, 0x6e, 0x52, 0x05, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x32, 0xaa, 0x0a, 0x0a, 0x0b, - 0x41, 0x75, 0x74, 0x68, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x51, 0x0a, 0x0d, 0x4c, - 0x6f, 0x67, 0x69, 0x6e, 0x50, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x12, 0x23, 0x2e, 0x68, - 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x6f, 0x67, - 0x69, 0x6e, 0x50, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, - 0x74, 0x1a, 0x1b, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, - 0x31, 0x2e, 0x41, 0x75, 0x74, 0x68, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x55, - 0x0a, 0x0f, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x54, 0x68, 0x69, 0x72, 0x64, 0x50, 0x61, 0x72, 0x74, - 0x79, 0x12, 0x25, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, - 0x31, 0x2e, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x54, 0x68, 0x69, 0x72, 0x64, 0x50, 0x61, 0x72, 0x74, - 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1b, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, - 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x75, 0x74, 0x68, 0x52, 0x65, 0x73, - 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x5b, 0x0a, 0x12, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, - 0x72, 0x54, 0x68, 0x69, 0x72, 0x64, 0x50, 0x61, 0x72, 0x74, 0x79, 0x12, 0x28, 0x2e, 0x68, 0x79, - 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x67, 0x69, - 0x73, 0x74, 0x65, 0x72, 0x54, 0x68, 0x69, 0x72, 0x64, 0x50, 0x61, 0x72, 0x74, 0x79, 0x52, 0x65, - 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1b, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, - 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x75, 0x74, 0x68, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, - 0x73, 0x65, 0x12, 0x7e, 0x0a, 0x19, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x54, 0x68, 0x69, 0x72, 0x64, - 0x50, 0x61, 0x72, 0x74, 0x79, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x65, 0x64, 0x12, - 0x2f, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, + 0x66, 0x69, 0x67, 0x22, 0x7d, 0x0a, 0x13, 0x41, 0x70, 0x70, 0x48, 0x65, 0x61, 0x72, 0x74, 0x62, + 0x65, 0x61, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2e, 0x0a, 0x04, 0x6d, 0x65, + 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, + 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x4d, 0x65, 0x74, 0x61, 0x52, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x12, 0x17, 0x0a, 0x07, 0x75, 0x73, + 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x06, 0x75, 0x73, 0x65, + 0x72, 0x49, 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x5f, 0x69, + 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, + 0x49, 0x64, 0x22, 0xb0, 0x01, 0x0a, 0x14, 0x41, 0x70, 0x70, 0x48, 0x65, 0x61, 0x72, 0x74, 0x62, + 0x65, 0x61, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x1a, 0x0a, 0x08, 0x61, + 0x63, 0x63, 0x65, 0x70, 0x74, 0x65, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x08, 0x61, + 0x63, 0x63, 0x65, 0x70, 0x74, 0x65, 0x64, 0x12, 0x26, 0x0a, 0x0f, 0x68, 0x65, 0x61, 0x72, 0x74, + 0x62, 0x65, 0x61, 0x74, 0x5f, 0x61, 0x74, 0x5f, 0x6d, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, + 0x52, 0x0d, 0x68, 0x65, 0x61, 0x72, 0x74, 0x62, 0x65, 0x61, 0x74, 0x41, 0x74, 0x4d, 0x73, 0x12, + 0x24, 0x0a, 0x0e, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x5f, 0x6d, + 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x54, + 0x69, 0x6d, 0x65, 0x4d, 0x73, 0x12, 0x2e, 0x0a, 0x05, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x04, + 0x20, 0x01, 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, + 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x75, 0x74, 0x68, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x52, 0x05, + 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x32, 0xba, 0x0b, 0x0a, 0x0b, 0x41, 0x75, 0x74, 0x68, 0x53, 0x65, + 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x51, 0x0a, 0x0d, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x50, 0x61, + 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x12, 0x23, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, + 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x50, 0x61, 0x73, 0x73, + 0x77, 0x6f, 0x72, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1b, 0x2e, 0x68, 0x79, + 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x75, 0x74, 0x68, + 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x55, 0x0a, 0x0f, 0x4c, 0x6f, 0x67, 0x69, + 0x6e, 0x54, 0x68, 0x69, 0x72, 0x64, 0x50, 0x61, 0x72, 0x74, 0x79, 0x12, 0x25, 0x2e, 0x68, 0x79, + 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x6f, 0x67, 0x69, + 0x6e, 0x54, 0x68, 0x69, 0x72, 0x64, 0x50, 0x61, 0x72, 0x74, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x1a, 0x1b, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, + 0x76, 0x31, 0x2e, 0x41, 0x75, 0x74, 0x68, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, + 0x5b, 0x0a, 0x12, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x54, 0x68, 0x69, 0x72, 0x64, + 0x50, 0x61, 0x72, 0x74, 0x79, 0x12, 0x28, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, + 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x54, 0x68, + 0x69, 0x72, 0x64, 0x50, 0x61, 0x72, 0x74, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, + 0x1b, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, + 0x41, 0x75, 0x74, 0x68, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x7e, 0x0a, 0x19, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x54, 0x68, 0x69, 0x72, 0x64, 0x50, 0x61, 0x72, 0x74, 0x79, 0x52, - 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x65, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, - 0x1a, 0x30, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, - 0x2e, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x54, 0x68, 0x69, 0x72, 0x64, 0x50, 0x61, 0x72, 0x74, 0x79, - 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x65, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, - 0x73, 0x65, 0x12, 0x54, 0x0a, 0x0b, 0x53, 0x65, 0x74, 0x50, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, - 0x64, 0x12, 0x21, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, - 0x31, 0x2e, 0x53, 0x65, 0x74, 0x50, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x52, 0x65, 0x71, - 0x75, 0x65, 0x73, 0x74, 0x1a, 0x22, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, - 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x65, 0x74, 0x50, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, - 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x69, 0x0a, 0x12, 0x51, 0x75, 0x69, 0x63, - 0x6b, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x28, - 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x51, - 0x75, 0x69, 0x63, 0x6b, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, - 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x29, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, + 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x65, 0x64, 0x12, 0x2f, 0x2e, 0x68, 0x79, 0x61, 0x70, + 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x54, + 0x68, 0x69, 0x72, 0x64, 0x50, 0x61, 0x72, 0x74, 0x79, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, + 0x72, 0x65, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x30, 0x2e, 0x68, 0x79, 0x61, + 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x68, 0x65, 0x63, 0x6b, + 0x54, 0x68, 0x69, 0x72, 0x64, 0x50, 0x61, 0x72, 0x74, 0x79, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, + 0x65, 0x72, 0x65, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x54, 0x0a, 0x0b, + 0x53, 0x65, 0x74, 0x50, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x12, 0x21, 0x2e, 0x68, 0x79, + 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x65, 0x74, 0x50, + 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x22, + 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x53, + 0x65, 0x74, 0x50, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x65, 0x12, 0x69, 0x0a, 0x12, 0x51, 0x75, 0x69, 0x63, 0x6b, 0x43, 0x72, 0x65, 0x61, 0x74, + 0x65, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x28, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x51, 0x75, 0x69, 0x63, 0x6b, 0x43, 0x72, - 0x65, 0x61, 0x74, 0x65, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, - 0x6e, 0x73, 0x65, 0x12, 0x57, 0x0a, 0x0c, 0x52, 0x65, 0x66, 0x72, 0x65, 0x73, 0x68, 0x54, 0x6f, - 0x6b, 0x65, 0x6e, 0x12, 0x22, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, - 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x66, 0x72, 0x65, 0x73, 0x68, 0x54, 0x6f, 0x6b, 0x65, 0x6e, - 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x23, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, - 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x66, 0x72, 0x65, 0x73, 0x68, 0x54, - 0x6f, 0x6b, 0x65, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x45, 0x0a, 0x06, - 0x4c, 0x6f, 0x67, 0x6f, 0x75, 0x74, 0x12, 0x1c, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, - 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x6f, 0x67, 0x6f, 0x75, 0x74, 0x52, 0x65, 0x71, - 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1d, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, - 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x6f, 0x67, 0x6f, 0x75, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, - 0x6e, 0x73, 0x65, 0x12, 0x69, 0x0a, 0x12, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x4c, 0x6f, 0x67, - 0x69, 0x6e, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x65, 0x64, 0x12, 0x28, 0x2e, 0x68, 0x79, 0x61, 0x70, - 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, - 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x65, 0x64, 0x52, 0x65, 0x71, 0x75, - 0x65, 0x73, 0x74, 0x1a, 0x29, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, + 0x65, 0x61, 0x74, 0x65, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x1a, 0x29, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, + 0x76, 0x31, 0x2e, 0x51, 0x75, 0x69, 0x63, 0x6b, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x41, 0x63, + 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x57, 0x0a, + 0x0c, 0x52, 0x65, 0x66, 0x72, 0x65, 0x73, 0x68, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x12, 0x22, 0x2e, + 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, + 0x66, 0x72, 0x65, 0x73, 0x68, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x1a, 0x23, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, + 0x31, 0x2e, 0x52, 0x65, 0x66, 0x72, 0x65, 0x73, 0x68, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x52, 0x65, + 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x45, 0x0a, 0x06, 0x4c, 0x6f, 0x67, 0x6f, 0x75, 0x74, + 0x12, 0x1c, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, + 0x2e, 0x4c, 0x6f, 0x67, 0x6f, 0x75, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1d, + 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x4c, + 0x6f, 0x67, 0x6f, 0x75, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x69, 0x0a, + 0x12, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x42, 0x6c, 0x6f, 0x63, + 0x6b, 0x65, 0x64, 0x12, 0x28, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x42, - 0x6c, 0x6f, 0x63, 0x6b, 0x65, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x7e, - 0x0a, 0x19, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x52, 0x69, 0x73, 0x6b, - 0x49, 0x50, 0x57, 0x68, 0x69, 0x74, 0x65, 0x6c, 0x69, 0x73, 0x74, 0x12, 0x2f, 0x2e, 0x68, 0x79, - 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x68, 0x65, 0x63, + 0x6c, 0x6f, 0x63, 0x6b, 0x65, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x29, 0x2e, + 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, + 0x63, 0x6f, 0x72, 0x64, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x65, 0x64, + 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x7e, 0x0a, 0x19, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x52, 0x69, 0x73, 0x6b, 0x49, 0x50, 0x57, 0x68, 0x69, 0x74, - 0x65, 0x6c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x30, 0x2e, 0x68, - 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x68, 0x65, - 0x63, 0x6b, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x52, 0x69, 0x73, 0x6b, 0x49, 0x50, 0x57, 0x68, 0x69, - 0x74, 0x65, 0x6c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x72, - 0x0a, 0x15, 0x47, 0x65, 0x74, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x52, 0x69, 0x73, - 0x6b, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x2b, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, - 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x52, 0x65, 0x67, 0x69, 0x73, - 0x74, 0x65, 0x72, 0x52, 0x69, 0x73, 0x6b, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x65, 0x71, - 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2c, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, - 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, - 0x52, 0x69, 0x73, 0x6b, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, - 0x73, 0x65, 0x12, 0x7b, 0x0a, 0x18, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x52, 0x65, 0x67, 0x69, - 0x73, 0x74, 0x65, 0x72, 0x52, 0x69, 0x73, 0x6b, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x2e, - 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x55, - 0x70, 0x64, 0x61, 0x74, 0x65, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x52, 0x69, 0x73, - 0x6b, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2f, - 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x55, - 0x70, 0x64, 0x61, 0x74, 0x65, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x52, 0x69, 0x73, - 0x6b, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, - 0x57, 0x0a, 0x0c, 0x41, 0x70, 0x70, 0x48, 0x65, 0x61, 0x72, 0x74, 0x62, 0x65, 0x61, 0x74, 0x12, - 0x22, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, - 0x41, 0x70, 0x70, 0x48, 0x65, 0x61, 0x72, 0x74, 0x62, 0x65, 0x61, 0x74, 0x52, 0x65, 0x71, 0x75, - 0x65, 0x73, 0x74, 0x1a, 0x23, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, - 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x70, 0x70, 0x48, 0x65, 0x61, 0x72, 0x74, 0x62, 0x65, 0x61, 0x74, - 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x42, 0x26, 0x5a, 0x24, 0x68, 0x79, 0x61, 0x70, - 0x70, 0x2e, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x70, 0x72, 0x6f, 0x74, - 0x6f, 0x2f, 0x75, 0x73, 0x65, 0x72, 0x2f, 0x76, 0x31, 0x3b, 0x75, 0x73, 0x65, 0x72, 0x76, 0x31, - 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x65, 0x6c, 0x69, 0x73, 0x74, 0x12, 0x2f, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, + 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x4c, 0x6f, 0x67, 0x69, 0x6e, + 0x52, 0x69, 0x73, 0x6b, 0x49, 0x50, 0x57, 0x68, 0x69, 0x74, 0x65, 0x6c, 0x69, 0x73, 0x74, 0x52, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x30, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, + 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x4c, 0x6f, 0x67, 0x69, + 0x6e, 0x52, 0x69, 0x73, 0x6b, 0x49, 0x50, 0x57, 0x68, 0x69, 0x74, 0x65, 0x6c, 0x69, 0x73, 0x74, + 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x8d, 0x01, 0x0a, 0x1e, 0x52, 0x65, 0x66, + 0x72, 0x65, 0x73, 0x68, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x52, 0x69, 0x73, 0x6b, 0x57, 0x68, 0x69, + 0x74, 0x65, 0x6c, 0x69, 0x73, 0x74, 0x43, 0x61, 0x63, 0x68, 0x65, 0x12, 0x34, 0x2e, 0x68, 0x79, + 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x66, 0x72, + 0x65, 0x73, 0x68, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x52, 0x69, 0x73, 0x6b, 0x57, 0x68, 0x69, 0x74, + 0x65, 0x6c, 0x69, 0x73, 0x74, 0x43, 0x61, 0x63, 0x68, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x1a, 0x35, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, + 0x31, 0x2e, 0x52, 0x65, 0x66, 0x72, 0x65, 0x73, 0x68, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x52, 0x69, + 0x73, 0x6b, 0x57, 0x68, 0x69, 0x74, 0x65, 0x6c, 0x69, 0x73, 0x74, 0x43, 0x61, 0x63, 0x68, 0x65, + 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x72, 0x0a, 0x15, 0x47, 0x65, 0x74, 0x52, + 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x52, 0x69, 0x73, 0x6b, 0x43, 0x6f, 0x6e, 0x66, 0x69, + 0x67, 0x12, 0x2b, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, + 0x31, 0x2e, 0x47, 0x65, 0x74, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x52, 0x69, 0x73, + 0x6b, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2c, + 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x47, + 0x65, 0x74, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x52, 0x69, 0x73, 0x6b, 0x43, 0x6f, + 0x6e, 0x66, 0x69, 0x67, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x7b, 0x0a, 0x18, + 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x52, 0x69, + 0x73, 0x6b, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x2e, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, + 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x52, + 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x52, 0x69, 0x73, 0x6b, 0x43, 0x6f, 0x6e, 0x66, 0x69, + 0x67, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2f, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, + 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x52, + 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x52, 0x69, 0x73, 0x6b, 0x43, 0x6f, 0x6e, 0x66, 0x69, + 0x67, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x57, 0x0a, 0x0c, 0x41, 0x70, 0x70, + 0x48, 0x65, 0x61, 0x72, 0x74, 0x62, 0x65, 0x61, 0x74, 0x12, 0x22, 0x2e, 0x68, 0x79, 0x61, 0x70, + 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x70, 0x70, 0x48, 0x65, 0x61, + 0x72, 0x74, 0x62, 0x65, 0x61, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x23, 0x2e, + 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x70, + 0x70, 0x48, 0x65, 0x61, 0x72, 0x74, 0x62, 0x65, 0x61, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x65, 0x42, 0x26, 0x5a, 0x24, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x6c, 0x6f, 0x63, 0x61, + 0x6c, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x75, 0x73, 0x65, 0x72, + 0x2f, 0x76, 0x31, 0x3b, 0x75, 0x73, 0x65, 0x72, 0x76, 0x31, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, + 0x6f, 0x33, } var ( @@ -2233,87 +2378,92 @@ func file_proto_user_v1_auth_proto_rawDescGZIP() []byte { return file_proto_user_v1_auth_proto_rawDescData } -var file_proto_user_v1_auth_proto_msgTypes = make([]protoimpl.MessageInfo, 25) +var file_proto_user_v1_auth_proto_msgTypes = make([]protoimpl.MessageInfo, 27) var file_proto_user_v1_auth_proto_goTypes = []any{ - (*LoginPasswordRequest)(nil), // 0: hyapp.user.v1.LoginPasswordRequest - (*LoginThirdPartyRequest)(nil), // 1: hyapp.user.v1.LoginThirdPartyRequest - (*RegisterThirdPartyRequest)(nil), // 2: hyapp.user.v1.RegisterThirdPartyRequest - (*CheckThirdPartyRegisteredRequest)(nil), // 3: hyapp.user.v1.CheckThirdPartyRegisteredRequest - (*CheckThirdPartyRegisteredResponse)(nil), // 4: hyapp.user.v1.CheckThirdPartyRegisteredResponse - (*AuthResponse)(nil), // 5: hyapp.user.v1.AuthResponse - (*SetPasswordRequest)(nil), // 6: hyapp.user.v1.SetPasswordRequest - (*SetPasswordResponse)(nil), // 7: hyapp.user.v1.SetPasswordResponse - (*QuickCreateAccountRequest)(nil), // 8: hyapp.user.v1.QuickCreateAccountRequest - (*QuickCreateAccountResponse)(nil), // 9: hyapp.user.v1.QuickCreateAccountResponse - (*RefreshTokenRequest)(nil), // 10: hyapp.user.v1.RefreshTokenRequest - (*RefreshTokenResponse)(nil), // 11: hyapp.user.v1.RefreshTokenResponse - (*LogoutRequest)(nil), // 12: hyapp.user.v1.LogoutRequest - (*LogoutResponse)(nil), // 13: hyapp.user.v1.LogoutResponse - (*RecordLoginBlockedRequest)(nil), // 14: hyapp.user.v1.RecordLoginBlockedRequest - (*RecordLoginBlockedResponse)(nil), // 15: hyapp.user.v1.RecordLoginBlockedResponse - (*CheckLoginRiskIPWhitelistRequest)(nil), // 16: hyapp.user.v1.CheckLoginRiskIPWhitelistRequest - (*CheckLoginRiskIPWhitelistResponse)(nil), // 17: hyapp.user.v1.CheckLoginRiskIPWhitelistResponse - (*RegisterRiskConfig)(nil), // 18: hyapp.user.v1.RegisterRiskConfig - (*GetRegisterRiskConfigRequest)(nil), // 19: hyapp.user.v1.GetRegisterRiskConfigRequest - (*GetRegisterRiskConfigResponse)(nil), // 20: hyapp.user.v1.GetRegisterRiskConfigResponse - (*UpdateRegisterRiskConfigRequest)(nil), // 21: hyapp.user.v1.UpdateRegisterRiskConfigRequest - (*UpdateRegisterRiskConfigResponse)(nil), // 22: hyapp.user.v1.UpdateRegisterRiskConfigResponse - (*AppHeartbeatRequest)(nil), // 23: hyapp.user.v1.AppHeartbeatRequest - (*AppHeartbeatResponse)(nil), // 24: hyapp.user.v1.AppHeartbeatResponse - (*RequestMeta)(nil), // 25: hyapp.user.v1.RequestMeta - (*AuthToken)(nil), // 26: hyapp.user.v1.AuthToken + (*LoginPasswordRequest)(nil), // 0: hyapp.user.v1.LoginPasswordRequest + (*LoginThirdPartyRequest)(nil), // 1: hyapp.user.v1.LoginThirdPartyRequest + (*RegisterThirdPartyRequest)(nil), // 2: hyapp.user.v1.RegisterThirdPartyRequest + (*CheckThirdPartyRegisteredRequest)(nil), // 3: hyapp.user.v1.CheckThirdPartyRegisteredRequest + (*CheckThirdPartyRegisteredResponse)(nil), // 4: hyapp.user.v1.CheckThirdPartyRegisteredResponse + (*AuthResponse)(nil), // 5: hyapp.user.v1.AuthResponse + (*SetPasswordRequest)(nil), // 6: hyapp.user.v1.SetPasswordRequest + (*SetPasswordResponse)(nil), // 7: hyapp.user.v1.SetPasswordResponse + (*QuickCreateAccountRequest)(nil), // 8: hyapp.user.v1.QuickCreateAccountRequest + (*QuickCreateAccountResponse)(nil), // 9: hyapp.user.v1.QuickCreateAccountResponse + (*RefreshTokenRequest)(nil), // 10: hyapp.user.v1.RefreshTokenRequest + (*RefreshTokenResponse)(nil), // 11: hyapp.user.v1.RefreshTokenResponse + (*LogoutRequest)(nil), // 12: hyapp.user.v1.LogoutRequest + (*LogoutResponse)(nil), // 13: hyapp.user.v1.LogoutResponse + (*RecordLoginBlockedRequest)(nil), // 14: hyapp.user.v1.RecordLoginBlockedRequest + (*RecordLoginBlockedResponse)(nil), // 15: hyapp.user.v1.RecordLoginBlockedResponse + (*CheckLoginRiskIPWhitelistRequest)(nil), // 16: hyapp.user.v1.CheckLoginRiskIPWhitelistRequest + (*CheckLoginRiskIPWhitelistResponse)(nil), // 17: hyapp.user.v1.CheckLoginRiskIPWhitelistResponse + (*RefreshLoginRiskWhitelistCacheRequest)(nil), // 18: hyapp.user.v1.RefreshLoginRiskWhitelistCacheRequest + (*RefreshLoginRiskWhitelistCacheResponse)(nil), // 19: hyapp.user.v1.RefreshLoginRiskWhitelistCacheResponse + (*RegisterRiskConfig)(nil), // 20: hyapp.user.v1.RegisterRiskConfig + (*GetRegisterRiskConfigRequest)(nil), // 21: hyapp.user.v1.GetRegisterRiskConfigRequest + (*GetRegisterRiskConfigResponse)(nil), // 22: hyapp.user.v1.GetRegisterRiskConfigResponse + (*UpdateRegisterRiskConfigRequest)(nil), // 23: hyapp.user.v1.UpdateRegisterRiskConfigRequest + (*UpdateRegisterRiskConfigResponse)(nil), // 24: hyapp.user.v1.UpdateRegisterRiskConfigResponse + (*AppHeartbeatRequest)(nil), // 25: hyapp.user.v1.AppHeartbeatRequest + (*AppHeartbeatResponse)(nil), // 26: hyapp.user.v1.AppHeartbeatResponse + (*RequestMeta)(nil), // 27: hyapp.user.v1.RequestMeta + (*AuthToken)(nil), // 28: hyapp.user.v1.AuthToken } var file_proto_user_v1_auth_proto_depIdxs = []int32{ - 25, // 0: hyapp.user.v1.LoginPasswordRequest.meta:type_name -> hyapp.user.v1.RequestMeta - 25, // 1: hyapp.user.v1.LoginThirdPartyRequest.meta:type_name -> hyapp.user.v1.RequestMeta - 25, // 2: hyapp.user.v1.RegisterThirdPartyRequest.meta:type_name -> hyapp.user.v1.RequestMeta - 25, // 3: hyapp.user.v1.CheckThirdPartyRegisteredRequest.meta:type_name -> hyapp.user.v1.RequestMeta - 26, // 4: hyapp.user.v1.AuthResponse.token:type_name -> hyapp.user.v1.AuthToken - 25, // 5: hyapp.user.v1.SetPasswordRequest.meta:type_name -> hyapp.user.v1.RequestMeta - 25, // 6: hyapp.user.v1.QuickCreateAccountRequest.meta:type_name -> hyapp.user.v1.RequestMeta - 26, // 7: hyapp.user.v1.QuickCreateAccountResponse.token:type_name -> hyapp.user.v1.AuthToken - 25, // 8: hyapp.user.v1.RefreshTokenRequest.meta:type_name -> hyapp.user.v1.RequestMeta - 26, // 9: hyapp.user.v1.RefreshTokenResponse.token:type_name -> hyapp.user.v1.AuthToken - 25, // 10: hyapp.user.v1.LogoutRequest.meta:type_name -> hyapp.user.v1.RequestMeta - 25, // 11: hyapp.user.v1.RecordLoginBlockedRequest.meta:type_name -> hyapp.user.v1.RequestMeta - 25, // 12: hyapp.user.v1.CheckLoginRiskIPWhitelistRequest.meta:type_name -> hyapp.user.v1.RequestMeta - 25, // 13: hyapp.user.v1.GetRegisterRiskConfigRequest.meta:type_name -> hyapp.user.v1.RequestMeta - 18, // 14: hyapp.user.v1.GetRegisterRiskConfigResponse.config:type_name -> hyapp.user.v1.RegisterRiskConfig - 25, // 15: hyapp.user.v1.UpdateRegisterRiskConfigRequest.meta:type_name -> hyapp.user.v1.RequestMeta - 18, // 16: hyapp.user.v1.UpdateRegisterRiskConfigResponse.config:type_name -> hyapp.user.v1.RegisterRiskConfig - 25, // 17: hyapp.user.v1.AppHeartbeatRequest.meta:type_name -> hyapp.user.v1.RequestMeta - 26, // 18: hyapp.user.v1.AppHeartbeatResponse.token:type_name -> hyapp.user.v1.AuthToken - 0, // 19: hyapp.user.v1.AuthService.LoginPassword:input_type -> hyapp.user.v1.LoginPasswordRequest - 1, // 20: hyapp.user.v1.AuthService.LoginThirdParty:input_type -> hyapp.user.v1.LoginThirdPartyRequest - 2, // 21: hyapp.user.v1.AuthService.RegisterThirdParty:input_type -> hyapp.user.v1.RegisterThirdPartyRequest - 3, // 22: hyapp.user.v1.AuthService.CheckThirdPartyRegistered:input_type -> hyapp.user.v1.CheckThirdPartyRegisteredRequest - 6, // 23: hyapp.user.v1.AuthService.SetPassword:input_type -> hyapp.user.v1.SetPasswordRequest - 8, // 24: hyapp.user.v1.AuthService.QuickCreateAccount:input_type -> hyapp.user.v1.QuickCreateAccountRequest - 10, // 25: hyapp.user.v1.AuthService.RefreshToken:input_type -> hyapp.user.v1.RefreshTokenRequest - 12, // 26: hyapp.user.v1.AuthService.Logout:input_type -> hyapp.user.v1.LogoutRequest - 14, // 27: hyapp.user.v1.AuthService.RecordLoginBlocked:input_type -> hyapp.user.v1.RecordLoginBlockedRequest - 16, // 28: hyapp.user.v1.AuthService.CheckLoginRiskIPWhitelist:input_type -> hyapp.user.v1.CheckLoginRiskIPWhitelistRequest - 19, // 29: hyapp.user.v1.AuthService.GetRegisterRiskConfig:input_type -> hyapp.user.v1.GetRegisterRiskConfigRequest - 21, // 30: hyapp.user.v1.AuthService.UpdateRegisterRiskConfig:input_type -> hyapp.user.v1.UpdateRegisterRiskConfigRequest - 23, // 31: hyapp.user.v1.AuthService.AppHeartbeat:input_type -> hyapp.user.v1.AppHeartbeatRequest - 5, // 32: hyapp.user.v1.AuthService.LoginPassword:output_type -> hyapp.user.v1.AuthResponse - 5, // 33: hyapp.user.v1.AuthService.LoginThirdParty:output_type -> hyapp.user.v1.AuthResponse - 5, // 34: hyapp.user.v1.AuthService.RegisterThirdParty:output_type -> hyapp.user.v1.AuthResponse - 4, // 35: hyapp.user.v1.AuthService.CheckThirdPartyRegistered:output_type -> hyapp.user.v1.CheckThirdPartyRegisteredResponse - 7, // 36: hyapp.user.v1.AuthService.SetPassword:output_type -> hyapp.user.v1.SetPasswordResponse - 9, // 37: hyapp.user.v1.AuthService.QuickCreateAccount:output_type -> hyapp.user.v1.QuickCreateAccountResponse - 11, // 38: hyapp.user.v1.AuthService.RefreshToken:output_type -> hyapp.user.v1.RefreshTokenResponse - 13, // 39: hyapp.user.v1.AuthService.Logout:output_type -> hyapp.user.v1.LogoutResponse - 15, // 40: hyapp.user.v1.AuthService.RecordLoginBlocked:output_type -> hyapp.user.v1.RecordLoginBlockedResponse - 17, // 41: hyapp.user.v1.AuthService.CheckLoginRiskIPWhitelist:output_type -> hyapp.user.v1.CheckLoginRiskIPWhitelistResponse - 20, // 42: hyapp.user.v1.AuthService.GetRegisterRiskConfig:output_type -> hyapp.user.v1.GetRegisterRiskConfigResponse - 22, // 43: hyapp.user.v1.AuthService.UpdateRegisterRiskConfig:output_type -> hyapp.user.v1.UpdateRegisterRiskConfigResponse - 24, // 44: hyapp.user.v1.AuthService.AppHeartbeat:output_type -> hyapp.user.v1.AppHeartbeatResponse - 32, // [32:45] is the sub-list for method output_type - 19, // [19:32] is the sub-list for method input_type - 19, // [19:19] is the sub-list for extension type_name - 19, // [19:19] is the sub-list for extension extendee - 0, // [0:19] is the sub-list for field type_name + 27, // 0: hyapp.user.v1.LoginPasswordRequest.meta:type_name -> hyapp.user.v1.RequestMeta + 27, // 1: hyapp.user.v1.LoginThirdPartyRequest.meta:type_name -> hyapp.user.v1.RequestMeta + 27, // 2: hyapp.user.v1.RegisterThirdPartyRequest.meta:type_name -> hyapp.user.v1.RequestMeta + 27, // 3: hyapp.user.v1.CheckThirdPartyRegisteredRequest.meta:type_name -> hyapp.user.v1.RequestMeta + 28, // 4: hyapp.user.v1.AuthResponse.token:type_name -> hyapp.user.v1.AuthToken + 27, // 5: hyapp.user.v1.SetPasswordRequest.meta:type_name -> hyapp.user.v1.RequestMeta + 27, // 6: hyapp.user.v1.QuickCreateAccountRequest.meta:type_name -> hyapp.user.v1.RequestMeta + 28, // 7: hyapp.user.v1.QuickCreateAccountResponse.token:type_name -> hyapp.user.v1.AuthToken + 27, // 8: hyapp.user.v1.RefreshTokenRequest.meta:type_name -> hyapp.user.v1.RequestMeta + 28, // 9: hyapp.user.v1.RefreshTokenResponse.token:type_name -> hyapp.user.v1.AuthToken + 27, // 10: hyapp.user.v1.LogoutRequest.meta:type_name -> hyapp.user.v1.RequestMeta + 27, // 11: hyapp.user.v1.RecordLoginBlockedRequest.meta:type_name -> hyapp.user.v1.RequestMeta + 27, // 12: hyapp.user.v1.CheckLoginRiskIPWhitelistRequest.meta:type_name -> hyapp.user.v1.RequestMeta + 27, // 13: hyapp.user.v1.RefreshLoginRiskWhitelistCacheRequest.meta:type_name -> hyapp.user.v1.RequestMeta + 27, // 14: hyapp.user.v1.GetRegisterRiskConfigRequest.meta:type_name -> hyapp.user.v1.RequestMeta + 20, // 15: hyapp.user.v1.GetRegisterRiskConfigResponse.config:type_name -> hyapp.user.v1.RegisterRiskConfig + 27, // 16: hyapp.user.v1.UpdateRegisterRiskConfigRequest.meta:type_name -> hyapp.user.v1.RequestMeta + 20, // 17: hyapp.user.v1.UpdateRegisterRiskConfigResponse.config:type_name -> hyapp.user.v1.RegisterRiskConfig + 27, // 18: hyapp.user.v1.AppHeartbeatRequest.meta:type_name -> hyapp.user.v1.RequestMeta + 28, // 19: hyapp.user.v1.AppHeartbeatResponse.token:type_name -> hyapp.user.v1.AuthToken + 0, // 20: hyapp.user.v1.AuthService.LoginPassword:input_type -> hyapp.user.v1.LoginPasswordRequest + 1, // 21: hyapp.user.v1.AuthService.LoginThirdParty:input_type -> hyapp.user.v1.LoginThirdPartyRequest + 2, // 22: hyapp.user.v1.AuthService.RegisterThirdParty:input_type -> hyapp.user.v1.RegisterThirdPartyRequest + 3, // 23: hyapp.user.v1.AuthService.CheckThirdPartyRegistered:input_type -> hyapp.user.v1.CheckThirdPartyRegisteredRequest + 6, // 24: hyapp.user.v1.AuthService.SetPassword:input_type -> hyapp.user.v1.SetPasswordRequest + 8, // 25: hyapp.user.v1.AuthService.QuickCreateAccount:input_type -> hyapp.user.v1.QuickCreateAccountRequest + 10, // 26: hyapp.user.v1.AuthService.RefreshToken:input_type -> hyapp.user.v1.RefreshTokenRequest + 12, // 27: hyapp.user.v1.AuthService.Logout:input_type -> hyapp.user.v1.LogoutRequest + 14, // 28: hyapp.user.v1.AuthService.RecordLoginBlocked:input_type -> hyapp.user.v1.RecordLoginBlockedRequest + 16, // 29: hyapp.user.v1.AuthService.CheckLoginRiskIPWhitelist:input_type -> hyapp.user.v1.CheckLoginRiskIPWhitelistRequest + 18, // 30: hyapp.user.v1.AuthService.RefreshLoginRiskWhitelistCache:input_type -> hyapp.user.v1.RefreshLoginRiskWhitelistCacheRequest + 21, // 31: hyapp.user.v1.AuthService.GetRegisterRiskConfig:input_type -> hyapp.user.v1.GetRegisterRiskConfigRequest + 23, // 32: hyapp.user.v1.AuthService.UpdateRegisterRiskConfig:input_type -> hyapp.user.v1.UpdateRegisterRiskConfigRequest + 25, // 33: hyapp.user.v1.AuthService.AppHeartbeat:input_type -> hyapp.user.v1.AppHeartbeatRequest + 5, // 34: hyapp.user.v1.AuthService.LoginPassword:output_type -> hyapp.user.v1.AuthResponse + 5, // 35: hyapp.user.v1.AuthService.LoginThirdParty:output_type -> hyapp.user.v1.AuthResponse + 5, // 36: hyapp.user.v1.AuthService.RegisterThirdParty:output_type -> hyapp.user.v1.AuthResponse + 4, // 37: hyapp.user.v1.AuthService.CheckThirdPartyRegistered:output_type -> hyapp.user.v1.CheckThirdPartyRegisteredResponse + 7, // 38: hyapp.user.v1.AuthService.SetPassword:output_type -> hyapp.user.v1.SetPasswordResponse + 9, // 39: hyapp.user.v1.AuthService.QuickCreateAccount:output_type -> hyapp.user.v1.QuickCreateAccountResponse + 11, // 40: hyapp.user.v1.AuthService.RefreshToken:output_type -> hyapp.user.v1.RefreshTokenResponse + 13, // 41: hyapp.user.v1.AuthService.Logout:output_type -> hyapp.user.v1.LogoutResponse + 15, // 42: hyapp.user.v1.AuthService.RecordLoginBlocked:output_type -> hyapp.user.v1.RecordLoginBlockedResponse + 17, // 43: hyapp.user.v1.AuthService.CheckLoginRiskIPWhitelist:output_type -> hyapp.user.v1.CheckLoginRiskIPWhitelistResponse + 19, // 44: hyapp.user.v1.AuthService.RefreshLoginRiskWhitelistCache:output_type -> hyapp.user.v1.RefreshLoginRiskWhitelistCacheResponse + 22, // 45: hyapp.user.v1.AuthService.GetRegisterRiskConfig:output_type -> hyapp.user.v1.GetRegisterRiskConfigResponse + 24, // 46: hyapp.user.v1.AuthService.UpdateRegisterRiskConfig:output_type -> hyapp.user.v1.UpdateRegisterRiskConfigResponse + 26, // 47: hyapp.user.v1.AuthService.AppHeartbeat:output_type -> hyapp.user.v1.AppHeartbeatResponse + 34, // [34:48] is the sub-list for method output_type + 20, // [20:34] is the sub-list for method input_type + 20, // [20:20] is the sub-list for extension type_name + 20, // [20:20] is the sub-list for extension extendee + 0, // [0:20] is the sub-list for field type_name } func init() { file_proto_user_v1_auth_proto_init() } @@ -2328,7 +2478,7 @@ func file_proto_user_v1_auth_proto_init() { GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: file_proto_user_v1_auth_proto_rawDesc, NumEnums: 0, - NumMessages: 25, + NumMessages: 27, NumExtensions: 0, NumServices: 1, }, diff --git a/api/proto/user/v1/auth.proto b/api/proto/user/v1/auth.proto index 0d28ce49..01cca86a 100644 --- a/api/proto/user/v1/auth.proto +++ b/api/proto/user/v1/auth.proto @@ -171,6 +171,19 @@ message CheckLoginRiskIPWhitelistResponse { bool whitelisted = 1; } +// RefreshLoginRiskWhitelistCacheRequest 要求 user-service 从 MySQL 重建登录风控白名单缓存。 +message RefreshLoginRiskWhitelistCacheRequest { + RequestMeta meta = 1; +} + +// RefreshLoginRiskWhitelistCacheResponse 返回本次刷新后的白名单规模,便于后台保存后确认缓存已更新。 +message RefreshLoginRiskWhitelistCacheResponse { + bool refreshed = 1; + int32 ip_whitelist_count = 2; + int32 user_whitelist_count = 3; + int64 user_cache_expires_at_ms = 4; +} + // RegisterRiskConfig 是注册链路读取的风控配置。 message RegisterRiskConfig { string app_code = 1; @@ -226,6 +239,7 @@ service AuthService { rpc Logout(LogoutRequest) returns (LogoutResponse); rpc RecordLoginBlocked(RecordLoginBlockedRequest) returns (RecordLoginBlockedResponse); rpc CheckLoginRiskIPWhitelist(CheckLoginRiskIPWhitelistRequest) returns (CheckLoginRiskIPWhitelistResponse); + rpc RefreshLoginRiskWhitelistCache(RefreshLoginRiskWhitelistCacheRequest) returns (RefreshLoginRiskWhitelistCacheResponse); rpc GetRegisterRiskConfig(GetRegisterRiskConfigRequest) returns (GetRegisterRiskConfigResponse); rpc UpdateRegisterRiskConfig(UpdateRegisterRiskConfigRequest) returns (UpdateRegisterRiskConfigResponse); rpc AppHeartbeat(AppHeartbeatRequest) returns (AppHeartbeatResponse); diff --git a/api/proto/user/v1/auth_grpc.pb.go b/api/proto/user/v1/auth_grpc.pb.go index a4f386c1..6f7e7e85 100644 --- a/api/proto/user/v1/auth_grpc.pb.go +++ b/api/proto/user/v1/auth_grpc.pb.go @@ -19,19 +19,20 @@ import ( const _ = grpc.SupportPackageIsVersion9 const ( - AuthService_LoginPassword_FullMethodName = "/hyapp.user.v1.AuthService/LoginPassword" - AuthService_LoginThirdParty_FullMethodName = "/hyapp.user.v1.AuthService/LoginThirdParty" - AuthService_RegisterThirdParty_FullMethodName = "/hyapp.user.v1.AuthService/RegisterThirdParty" - AuthService_CheckThirdPartyRegistered_FullMethodName = "/hyapp.user.v1.AuthService/CheckThirdPartyRegistered" - AuthService_SetPassword_FullMethodName = "/hyapp.user.v1.AuthService/SetPassword" - AuthService_QuickCreateAccount_FullMethodName = "/hyapp.user.v1.AuthService/QuickCreateAccount" - AuthService_RefreshToken_FullMethodName = "/hyapp.user.v1.AuthService/RefreshToken" - AuthService_Logout_FullMethodName = "/hyapp.user.v1.AuthService/Logout" - AuthService_RecordLoginBlocked_FullMethodName = "/hyapp.user.v1.AuthService/RecordLoginBlocked" - AuthService_CheckLoginRiskIPWhitelist_FullMethodName = "/hyapp.user.v1.AuthService/CheckLoginRiskIPWhitelist" - AuthService_GetRegisterRiskConfig_FullMethodName = "/hyapp.user.v1.AuthService/GetRegisterRiskConfig" - AuthService_UpdateRegisterRiskConfig_FullMethodName = "/hyapp.user.v1.AuthService/UpdateRegisterRiskConfig" - AuthService_AppHeartbeat_FullMethodName = "/hyapp.user.v1.AuthService/AppHeartbeat" + AuthService_LoginPassword_FullMethodName = "/hyapp.user.v1.AuthService/LoginPassword" + AuthService_LoginThirdParty_FullMethodName = "/hyapp.user.v1.AuthService/LoginThirdParty" + AuthService_RegisterThirdParty_FullMethodName = "/hyapp.user.v1.AuthService/RegisterThirdParty" + AuthService_CheckThirdPartyRegistered_FullMethodName = "/hyapp.user.v1.AuthService/CheckThirdPartyRegistered" + AuthService_SetPassword_FullMethodName = "/hyapp.user.v1.AuthService/SetPassword" + AuthService_QuickCreateAccount_FullMethodName = "/hyapp.user.v1.AuthService/QuickCreateAccount" + AuthService_RefreshToken_FullMethodName = "/hyapp.user.v1.AuthService/RefreshToken" + AuthService_Logout_FullMethodName = "/hyapp.user.v1.AuthService/Logout" + AuthService_RecordLoginBlocked_FullMethodName = "/hyapp.user.v1.AuthService/RecordLoginBlocked" + AuthService_CheckLoginRiskIPWhitelist_FullMethodName = "/hyapp.user.v1.AuthService/CheckLoginRiskIPWhitelist" + AuthService_RefreshLoginRiskWhitelistCache_FullMethodName = "/hyapp.user.v1.AuthService/RefreshLoginRiskWhitelistCache" + AuthService_GetRegisterRiskConfig_FullMethodName = "/hyapp.user.v1.AuthService/GetRegisterRiskConfig" + AuthService_UpdateRegisterRiskConfig_FullMethodName = "/hyapp.user.v1.AuthService/UpdateRegisterRiskConfig" + AuthService_AppHeartbeat_FullMethodName = "/hyapp.user.v1.AuthService/AppHeartbeat" ) // AuthServiceClient is the client API for AuthService service. @@ -50,6 +51,7 @@ type AuthServiceClient interface { Logout(ctx context.Context, in *LogoutRequest, opts ...grpc.CallOption) (*LogoutResponse, error) RecordLoginBlocked(ctx context.Context, in *RecordLoginBlockedRequest, opts ...grpc.CallOption) (*RecordLoginBlockedResponse, error) CheckLoginRiskIPWhitelist(ctx context.Context, in *CheckLoginRiskIPWhitelistRequest, opts ...grpc.CallOption) (*CheckLoginRiskIPWhitelistResponse, error) + RefreshLoginRiskWhitelistCache(ctx context.Context, in *RefreshLoginRiskWhitelistCacheRequest, opts ...grpc.CallOption) (*RefreshLoginRiskWhitelistCacheResponse, error) GetRegisterRiskConfig(ctx context.Context, in *GetRegisterRiskConfigRequest, opts ...grpc.CallOption) (*GetRegisterRiskConfigResponse, error) UpdateRegisterRiskConfig(ctx context.Context, in *UpdateRegisterRiskConfigRequest, opts ...grpc.CallOption) (*UpdateRegisterRiskConfigResponse, error) AppHeartbeat(ctx context.Context, in *AppHeartbeatRequest, opts ...grpc.CallOption) (*AppHeartbeatResponse, error) @@ -163,6 +165,16 @@ func (c *authServiceClient) CheckLoginRiskIPWhitelist(ctx context.Context, in *C return out, nil } +func (c *authServiceClient) RefreshLoginRiskWhitelistCache(ctx context.Context, in *RefreshLoginRiskWhitelistCacheRequest, opts ...grpc.CallOption) (*RefreshLoginRiskWhitelistCacheResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(RefreshLoginRiskWhitelistCacheResponse) + err := c.cc.Invoke(ctx, AuthService_RefreshLoginRiskWhitelistCache_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + func (c *authServiceClient) GetRegisterRiskConfig(ctx context.Context, in *GetRegisterRiskConfigRequest, opts ...grpc.CallOption) (*GetRegisterRiskConfigResponse, error) { cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) out := new(GetRegisterRiskConfigResponse) @@ -209,6 +221,7 @@ type AuthServiceServer interface { Logout(context.Context, *LogoutRequest) (*LogoutResponse, error) RecordLoginBlocked(context.Context, *RecordLoginBlockedRequest) (*RecordLoginBlockedResponse, error) CheckLoginRiskIPWhitelist(context.Context, *CheckLoginRiskIPWhitelistRequest) (*CheckLoginRiskIPWhitelistResponse, error) + RefreshLoginRiskWhitelistCache(context.Context, *RefreshLoginRiskWhitelistCacheRequest) (*RefreshLoginRiskWhitelistCacheResponse, error) GetRegisterRiskConfig(context.Context, *GetRegisterRiskConfigRequest) (*GetRegisterRiskConfigResponse, error) UpdateRegisterRiskConfig(context.Context, *UpdateRegisterRiskConfigRequest) (*UpdateRegisterRiskConfigResponse, error) AppHeartbeat(context.Context, *AppHeartbeatRequest) (*AppHeartbeatResponse, error) @@ -252,6 +265,9 @@ func (UnimplementedAuthServiceServer) RecordLoginBlocked(context.Context, *Recor func (UnimplementedAuthServiceServer) CheckLoginRiskIPWhitelist(context.Context, *CheckLoginRiskIPWhitelistRequest) (*CheckLoginRiskIPWhitelistResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method CheckLoginRiskIPWhitelist not implemented") } +func (UnimplementedAuthServiceServer) RefreshLoginRiskWhitelistCache(context.Context, *RefreshLoginRiskWhitelistCacheRequest) (*RefreshLoginRiskWhitelistCacheResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method RefreshLoginRiskWhitelistCache not implemented") +} func (UnimplementedAuthServiceServer) GetRegisterRiskConfig(context.Context, *GetRegisterRiskConfigRequest) (*GetRegisterRiskConfigResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method GetRegisterRiskConfig not implemented") } @@ -462,6 +478,24 @@ func _AuthService_CheckLoginRiskIPWhitelist_Handler(srv interface{}, ctx context return interceptor(ctx, in, info, handler) } +func _AuthService_RefreshLoginRiskWhitelistCache_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(RefreshLoginRiskWhitelistCacheRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(AuthServiceServer).RefreshLoginRiskWhitelistCache(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: AuthService_RefreshLoginRiskWhitelistCache_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(AuthServiceServer).RefreshLoginRiskWhitelistCache(ctx, req.(*RefreshLoginRiskWhitelistCacheRequest)) + } + return interceptor(ctx, in, info, handler) +} + func _AuthService_GetRegisterRiskConfig_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(GetRegisterRiskConfigRequest) if err := dec(in); err != nil { @@ -563,6 +597,10 @@ var AuthService_ServiceDesc = grpc.ServiceDesc{ MethodName: "CheckLoginRiskIPWhitelist", Handler: _AuthService_CheckLoginRiskIPWhitelist_Handler, }, + { + MethodName: "RefreshLoginRiskWhitelistCache", + Handler: _AuthService_RefreshLoginRiskWhitelistCache_Handler, + }, { MethodName: "GetRegisterRiskConfig", Handler: _AuthService_GetRegisterRiskConfig_Handler, diff --git a/scripts/mysql/051_login_risk_user_whitelist.sql b/scripts/mysql/051_login_risk_user_whitelist.sql new file mode 100644 index 00000000..b7dcef3b --- /dev/null +++ b/scripts/mysql/051_login_risk_user_whitelist.sql @@ -0,0 +1,14 @@ +CREATE TABLE IF NOT EXISTS login_risk_user_whitelist ( + app_code VARCHAR(32) NOT NULL COMMENT '应用编码,用于多租户隔离', + whitelist_id BIGINT NOT NULL AUTO_INCREMENT COMMENT '白名单 ID', + user_id BIGINT NOT NULL COMMENT '允许跳过登录地区屏蔽复核的用户 ID', + enabled TINYINT(1) NOT NULL DEFAULT 1 COMMENT '启用', + created_by_user_id BIGINT NULL COMMENT '创建人用户 ID', + updated_by_user_id BIGINT NULL COMMENT '更新人用户 ID', + created_at_ms BIGINT NOT NULL COMMENT '创建时间,UTC epoch ms', + updated_at_ms BIGINT NOT NULL COMMENT '更新时间,UTC epoch ms', + PRIMARY KEY (whitelist_id), + UNIQUE KEY uk_login_risk_user_whitelist_user (app_code, user_id), + KEY idx_login_risk_user_whitelist_enabled (app_code, enabled, user_id), + KEY idx_login_risk_user_whitelist_updated (app_code, updated_at_ms) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='登录风险用户白名单表'; diff --git a/server/admin/cmd/server/main.go b/server/admin/cmd/server/main.go index c7470c82..9a01d697 100644 --- a/server/admin/cmd/server/main.go +++ b/server/admin/cmd/server/main.go @@ -294,7 +294,7 @@ func main() { RedPacket: redpacketmodule.New(walletclient.NewGRPC(walletConn), auditHandler), Report: reportmodule.New(userDB, roomClient), RegistrationReward: registrationrewardmodule.New(activityclient.NewGRPC(activityConn), userDB, auditHandler), - RegionBlock: regionblockmodule.New(userDB, auditHandler), + RegionBlock: regionblockmodule.New(userDB, userclient.NewGRPC(userConn), auditHandler), Resource: resourcemodule.New(walletclient.NewGRPC(walletConn), store, userDB, cfg.WalletService.RequestTimeout, auditHandler), RiskConfig: riskconfigmodule.New(userclient.NewGRPC(userConn), auditHandler), RoomAdmin: roomadminmodule.New(userDB, store, roomClient, robotClient, auditHandler), diff --git a/server/admin/internal/integration/userclient/client.go b/server/admin/internal/integration/userclient/client.go index 3319e2e6..7bf1fcbe 100644 --- a/server/admin/internal/integration/userclient/client.go +++ b/server/admin/internal/integration/userclient/client.go @@ -17,6 +17,7 @@ type Client interface { SetUserStatus(ctx context.Context, req SetUserStatusRequest) (*SetUserStatusResult, error) GetRegisterRiskConfig(ctx context.Context, req GetRegisterRiskConfigRequest) (*RegisterRiskConfig, error) UpdateRegisterRiskConfig(ctx context.Context, req UpdateRegisterRiskConfigRequest) (*RegisterRiskConfig, error) + RefreshLoginRiskWhitelistCache(ctx context.Context, req RefreshLoginRiskWhitelistCacheRequest) (*LoginRiskWhitelistCacheSnapshot, error) ResolveDisplayUserID(ctx context.Context, req ResolveDisplayUserIDRequest) (*UserIdentity, error) ListPrettyDisplayIDPools(ctx context.Context, req ListPrettyDisplayIDPoolsRequest) ([]*PrettyDisplayIDPool, int64, error) CreatePrettyDisplayIDPool(ctx context.Context, req PrettyDisplayIDPoolRequest) (*PrettyDisplayIDPool, error) @@ -139,6 +140,18 @@ type RegisterRiskConfig struct { UpdatedByAdminID int64 `json:"updatedByAdminId"` } +type RefreshLoginRiskWhitelistCacheRequest struct { + RequestID string + Caller string +} + +type LoginRiskWhitelistCacheSnapshot struct { + Refreshed bool `json:"refreshed"` + IPWhitelistCount int32 `json:"ipWhitelistCount"` + UserWhitelistCount int32 `json:"userWhitelistCount"` + UserCacheExpiresAtMs int64 `json:"userCacheExpiresAtMs"` +} + type ResolveDisplayUserIDRequest struct { RequestID string Caller string @@ -285,6 +298,21 @@ func (c *GRPCClient) UpdateRegisterRiskConfig(ctx context.Context, req UpdateReg return fromProtoRegisterRiskConfig(resp.GetConfig()), nil } +func (c *GRPCClient) RefreshLoginRiskWhitelistCache(ctx context.Context, req RefreshLoginRiskWhitelistCacheRequest) (*LoginRiskWhitelistCacheSnapshot, error) { + resp, err := c.authClient.RefreshLoginRiskWhitelistCache(ctx, &userv1.RefreshLoginRiskWhitelistCacheRequest{ + Meta: requestMeta(ctx, req.RequestID, req.Caller), + }) + if err != nil { + return nil, err + } + return &LoginRiskWhitelistCacheSnapshot{ + Refreshed: resp.GetRefreshed(), + IPWhitelistCount: resp.GetIpWhitelistCount(), + UserWhitelistCount: resp.GetUserWhitelistCount(), + UserCacheExpiresAtMs: resp.GetUserCacheExpiresAtMs(), + }, nil +} + // BatchGetUsers 复用 user-service 主数据 RPC,让后台列表只做展示聚合,不复制用户资料到游戏库。 func (c *GRPCClient) BatchGetUsers(ctx context.Context, req BatchGetUsersRequest) (map[int64]*User, error) { resp, err := c.client.BatchGetUsers(ctx, &userv1.BatchGetUsersRequest{ diff --git a/server/admin/internal/modules/regionblock/handler.go b/server/admin/internal/modules/regionblock/handler.go index 137e01e1..95e3962c 100644 --- a/server/admin/internal/modules/regionblock/handler.go +++ b/server/admin/internal/modules/regionblock/handler.go @@ -5,6 +5,7 @@ import ( "errors" "fmt" + "hyapp-admin-server/internal/integration/userclient" "hyapp-admin-server/internal/middleware" "hyapp-admin-server/internal/modules/shared" "hyapp-admin-server/internal/response" @@ -18,12 +19,13 @@ type Handler struct { } type replaceRequest struct { - Keywords *[]string `json:"keywords"` - WhitelistIPs *[]string `json:"whitelist_ips"` + Keywords *[]string `json:"keywords"` + WhitelistIPs *[]string `json:"whitelist_ips"` + WhitelistUserIDs *[]string `json:"whitelist_user_ids"` } -func New(userDB *sql.DB, audit shared.OperationLogger) *Handler { - return &Handler{service: NewService(userDB), audit: audit} +func New(userDB *sql.DB, user userclient.Client, audit shared.OperationLogger) *Handler { + return &Handler{service: NewService(userDB, user), audit: audit} } // ListRegionBlocks 返回用户管理 > 地区屏蔽页面需要的当前屏蔽词列表。 @@ -43,15 +45,19 @@ func (h *Handler) ReplaceRegionBlocks(c *gin.Context) { response.BadRequest(c, "地区屏蔽词参数不正确") return } - items, err := h.service.Replace(c.Request.Context(), int64(shared.ActorFromContext(c).UserID), *req.Keywords, req.WhitelistIPs) + items, err := h.service.Replace(c.Request.Context(), middleware.CurrentRequestID(c), int64(shared.ActorFromContext(c).UserID), *req.Keywords, req.WhitelistIPs, req.WhitelistUserIDs) if err != nil { if errors.Is(err, errInvalidPayload) { response.BadRequest(c, "地区屏蔽词参数不正确") return } + if errors.Is(err, errWhitelistCacheRefresh) { + response.ServerError(c, "保存成功但刷新登录风控缓存失败") + return + } response.ServerError(c, "保存地区屏蔽配置失败") return } - shared.OperationLogWithResourceID(c, h.audit, "replace-region-blocks", "login_risk_country_blocks", middleware.CurrentRequestID(c), "success", fmt.Sprintf("keywords=%d whitelist_ips=%d", len(items.Items), len(items.WhitelistItems))) + shared.OperationLogWithResourceID(c, h.audit, "replace-region-blocks", "login_risk_country_blocks", middleware.CurrentRequestID(c), "success", fmt.Sprintf("keywords=%d whitelist_ips=%d whitelist_users=%d", len(items.Items), len(items.WhitelistItems), len(items.WhitelistUserItems))) response.OK(c, items) } diff --git a/server/admin/internal/modules/regionblock/service.go b/server/admin/internal/modules/regionblock/service.go index fb68b239..fad006c2 100644 --- a/server/admin/internal/modules/regionblock/service.go +++ b/server/admin/internal/modules/regionblock/service.go @@ -6,26 +6,31 @@ import ( "errors" "net/netip" "regexp" + "strconv" "strings" "time" "unicode/utf8" "hyapp-admin-server/internal/appctx" + "hyapp-admin-server/internal/integration/userclient" ) const ( maxRegionBlockKeywords = 200 maxRegionBlockKeywordLength = 128 maxIPWhitelistItems = 500 + maxUserWhitelistItems = 500 ) var ( - countryCodePattern = regexp.MustCompile(`^[A-Za-z]{2,3}$`) - errInvalidPayload = errors.New("region block payload invalid") + countryCodePattern = regexp.MustCompile(`^[A-Za-z]{2,3}$`) + errInvalidPayload = errors.New("region block payload invalid") + errWhitelistCacheRefresh = errors.New("login risk whitelist cache refresh failed") ) type Service struct { userDB *sql.DB + user userclient.Client } type RegionBlock struct { @@ -45,11 +50,21 @@ type IPWhitelistItem struct { UpdatedAtMS int64 `json:"updated_at_ms"` } +type UserWhitelistItem struct { + WhitelistID int64 `json:"whitelist_id"` + UserID int64 `json:"user_id,string"` + Enabled bool `json:"enabled"` + CreatedAtMS int64 `json:"created_at_ms"` + UpdatedAtMS int64 `json:"updated_at_ms"` +} + type Config struct { - Items []RegionBlock `json:"items"` - Total int `json:"total"` - WhitelistItems []IPWhitelistItem `json:"whitelist_items"` - WhitelistTotal int `json:"whitelist_total"` + Items []RegionBlock `json:"items"` + Total int `json:"total"` + WhitelistItems []IPWhitelistItem `json:"whitelist_items"` + WhitelistTotal int `json:"whitelist_total"` + WhitelistUserItems []UserWhitelistItem `json:"whitelist_user_items"` + WhitelistUserTotal int `json:"whitelist_user_total"` } type normalizedKeyword struct { @@ -61,11 +76,11 @@ type countryCodeQuerier interface { QueryRowContext(ctx context.Context, query string, args ...any) *sql.Row } -func NewService(userDB *sql.DB) *Service { - return &Service{userDB: userDB} +func NewService(userDB *sql.DB, user userclient.Client) *Service { + return &Service{userDB: userDB, user: user} } -// List 返回当前 App 已启用的登录地区屏蔽词和 IP 白名单。 +// List 返回当前 App 已启用的登录地区屏蔽词、IP 白名单和用户白名单。 // 表属于 user-service 登录风控事实,admin-server 只作为后台维护入口直接写 user DB。 func (s *Service) List(ctx context.Context) (Config, error) { if s.userDB == nil { @@ -80,12 +95,16 @@ func (s *Service) List(ctx context.Context) (Config, error) { if err != nil { return Config{}, err } - return Config{Items: blocks, Total: len(blocks), WhitelistItems: whitelist, WhitelistTotal: len(whitelist)}, nil + userWhitelist, err := queryEnabledUserWhitelist(ctx, s.userDB, appCode) + if err != nil { + return Config{}, err + } + return Config{Items: blocks, Total: len(blocks), WhitelistItems: whitelist, WhitelistTotal: len(whitelist), WhitelistUserItems: userWhitelist, WhitelistUserTotal: len(userWhitelist)}, nil } // Replace 用页面提交的词表整体替换当前 App 的生效配置。 // 整体替换比逐条增删更适合配置页保存语义,也能避免客户端和服务端出现半旧半新的屏蔽词集合。 -func (s *Service) Replace(ctx context.Context, actorID int64, keywords []string, whitelistIPs *[]string) (Config, error) { +func (s *Service) Replace(ctx context.Context, requestID string, actorID int64, keywords []string, whitelistIPs *[]string, whitelistUserIDs *[]string) (Config, error) { if s.userDB == nil { return Config{}, errors.New("user db is not configured") } @@ -100,6 +119,13 @@ func (s *Service) Replace(ctx context.Context, actorID int64, keywords []string, return Config{}, err } } + var normalizedUserIDs []int64 + if whitelistUserIDs != nil { + normalizedUserIDs, err = normalizeUserIDWhitelist(*whitelistUserIDs) + if err != nil { + return Config{}, err + } + } tx, err := s.userDB.BeginTx(ctx, nil) if err != nil { return Config{}, err @@ -155,9 +181,38 @@ func (s *Service) Replace(ctx context.Context, actorID int64, keywords []string, } } } + if whitelistUserIDs != nil { + if _, err := tx.ExecContext(ctx, ` + UPDATE login_risk_user_whitelist + SET enabled = 0, updated_by_user_id = ?, updated_at_ms = ? + WHERE app_code = ? AND enabled = 1 + `, nullableActor(actorID), nowMS, appCode); err != nil { + return Config{}, err + } + for _, userID := range normalizedUserIDs { + if _, err := tx.ExecContext(ctx, ` + INSERT INTO login_risk_user_whitelist ( + app_code, user_id, enabled, + created_by_user_id, updated_by_user_id, created_at_ms, updated_at_ms + ) + VALUES (?, ?, 1, ?, ?, ?, ?) + ON DUPLICATE KEY UPDATE + enabled = 1, + updated_by_user_id = VALUES(updated_by_user_id), + updated_at_ms = VALUES(updated_at_ms) + `, appCode, userID, nullableActor(actorID), nullableActor(actorID), nowMS, nowMS); err != nil { + return Config{}, err + } + } + } if err := tx.Commit(); err != nil { return Config{}, err } + if whitelistIPs != nil || whitelistUserIDs != nil { + if err := s.refreshWhitelistCache(ctx, requestID); err != nil { + return Config{}, err + } + } return s.List(ctx) } @@ -210,6 +265,44 @@ func normalizeIPWhitelist(raw []string) ([]string, error) { return result, nil } +func normalizeUserIDWhitelist(raw []string) ([]int64, error) { + if len(raw) > maxUserWhitelistItems { + return nil, errInvalidPayload + } + result := make([]int64, 0, len(raw)) + seen := map[int64]struct{}{} + for _, item := range raw { + value := strings.TrimSpace(item) + if value == "" { + return nil, errInvalidPayload + } + userID, err := strconv.ParseInt(value, 10, 64) + if err != nil || userID <= 0 { + return nil, errInvalidPayload + } + if _, ok := seen[userID]; ok { + continue + } + seen[userID] = struct{}{} + result = append(result, userID) + } + return result, nil +} + +func (s *Service) refreshWhitelistCache(ctx context.Context, requestID string) error { + if s.user == nil { + return nil + } + _, err := s.user.RefreshLoginRiskWhitelistCache(ctx, userclient.RefreshLoginRiskWhitelistCacheRequest{ + RequestID: requestID, + Caller: "admin-server", + }) + if err != nil { + return errWhitelistCacheRefresh + } + return nil +} + func resolveCountryCode(ctx context.Context, querier countryCodeQuerier, appCode string, keyword string) (string, error) { if countryCodePattern.MatchString(keyword) { return strings.ToUpper(keyword), nil @@ -290,6 +383,34 @@ func queryEnabledIPWhitelist(ctx context.Context, db *sql.DB, appCode string) ([ return items, nil } +func queryEnabledUserWhitelist(ctx context.Context, db *sql.DB, appCode string) ([]UserWhitelistItem, error) { + rows, err := db.QueryContext(ctx, ` + SELECT whitelist_id, user_id, enabled, created_at_ms, updated_at_ms + FROM login_risk_user_whitelist + WHERE app_code = ? AND enabled = 1 + ORDER BY user_id ASC, whitelist_id ASC + `, appCode) + if err != nil { + return nil, err + } + defer rows.Close() + + items := make([]UserWhitelistItem, 0) + for rows.Next() { + var item UserWhitelistItem + if err := rows.Scan(&item.WhitelistID, &item.UserID, &item.Enabled, &item.CreatedAtMS, &item.UpdatedAtMS); err != nil { + return nil, err + } + if item.UserID > 0 { + items = append(items, item) + } + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + func nullableActor(actorID int64) any { if actorID <= 0 { return nil diff --git a/server/admin/internal/modules/regionblock/service_test.go b/server/admin/internal/modules/regionblock/service_test.go new file mode 100644 index 00000000..f8724114 --- /dev/null +++ b/server/admin/internal/modules/regionblock/service_test.go @@ -0,0 +1,59 @@ +package regionblock + +import ( + "context" + "errors" + "reflect" + "testing" + + "hyapp-admin-server/internal/integration/userclient" +) + +type fakeWhitelistCacheUserClient struct { + userclient.Client + + called bool + err error +} + +func (f *fakeWhitelistCacheUserClient) RefreshLoginRiskWhitelistCache(_ context.Context, req userclient.RefreshLoginRiskWhitelistCacheRequest) (*userclient.LoginRiskWhitelistCacheSnapshot, error) { + f.called = true + if req.RequestID != "req-refresh" || req.Caller != "admin-server" { + return nil, errors.New("unexpected refresh request") + } + if f.err != nil { + return nil, f.err + } + return &userclient.LoginRiskWhitelistCacheSnapshot{Refreshed: true, UserWhitelistCount: 2}, nil +} + +func TestNormalizeUserIDWhitelistDeduplicatesPositiveIDs(t *testing.T) { + got, err := normalizeUserIDWhitelist([]string{" 900025 ", "900026", "900025"}) + if err != nil { + t.Fatalf("normalize user whitelist failed: %v", err) + } + want := []int64{900025, 900026} + if !reflect.DeepEqual(got, want) { + t.Fatalf("normalized user whitelist = %+v, want %+v", got, want) + } +} + +func TestNormalizeUserIDWhitelistRejectsInvalidID(t *testing.T) { + for _, raw := range [][]string{{""}, {"0"}, {"-1"}, {"abc"}} { + if _, err := normalizeUserIDWhitelist(raw); !errors.Is(err, errInvalidPayload) { + t.Fatalf("normalize %v error = %v, want errInvalidPayload", raw, err) + } + } +} + +func TestRefreshWhitelistCacheMapsUserServiceError(t *testing.T) { + client := &fakeWhitelistCacheUserClient{err: errors.New("rpc down")} + service := NewService(nil, client) + err := service.refreshWhitelistCache(context.Background(), "req-refresh") + if !client.called { + t.Fatal("expected refresh rpc to be called") + } + if !errors.Is(err, errWhitelistCacheRefresh) { + t.Fatalf("refresh error = %v, want errWhitelistCacheRefresh", err) + } +} diff --git a/services/user-service/deploy/mysql/initdb/001_user_service.sql b/services/user-service/deploy/mysql/initdb/001_user_service.sql index 8dfb3c8a..3e2d7cce 100644 --- a/services/user-service/deploy/mysql/initdb/001_user_service.sql +++ b/services/user-service/deploy/mysql/initdb/001_user_service.sql @@ -1268,3 +1268,18 @@ CREATE TABLE IF NOT EXISTS login_risk_ip_whitelist ( KEY idx_login_risk_ip_whitelist_enabled (app_code, enabled, ip_address), KEY idx_login_risk_ip_whitelist_updated (app_code, updated_at_ms) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='登录风险 IP 白名单表'; + +CREATE TABLE IF NOT EXISTS login_risk_user_whitelist ( + app_code VARCHAR(32) NOT NULL COMMENT '应用编码,用于多租户隔离', + whitelist_id BIGINT NOT NULL AUTO_INCREMENT COMMENT '白名单 ID', + user_id BIGINT NOT NULL COMMENT '允许跳过登录地区屏蔽复核的用户 ID', + enabled TINYINT(1) NOT NULL DEFAULT 1 COMMENT '启用', + created_by_user_id BIGINT NULL COMMENT '创建人用户 ID', + updated_by_user_id BIGINT NULL COMMENT '更新人用户 ID', + created_at_ms BIGINT NOT NULL COMMENT '创建时间,UTC epoch ms', + updated_at_ms BIGINT NOT NULL COMMENT '更新时间,UTC epoch ms', + PRIMARY KEY (whitelist_id), + UNIQUE KEY uk_login_risk_user_whitelist_user (app_code, user_id), + KEY idx_login_risk_user_whitelist_enabled (app_code, enabled, user_id), + KEY idx_login_risk_user_whitelist_updated (app_code, updated_at_ms) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='登录风险用户白名单表'; diff --git a/services/user-service/internal/domain/auth/risk.go b/services/user-service/internal/domain/auth/risk.go index 5c5bd0bd..b26ed26b 100644 --- a/services/user-service/internal/domain/auth/risk.go +++ b/services/user-service/internal/domain/auth/risk.go @@ -83,6 +83,17 @@ type LoginRiskIPWhitelist struct { UpdatedAtMs int64 } +// LoginRiskUserWhitelist 是后台维护的登录地区屏蔽用户白名单事实。 +// 用户 ID 命中后只跳过登录后的地区/IP 复核,不改变账号状态、注册设备限制或登录前 gateway 快拦语义。 +type LoginRiskUserWhitelist struct { + AppCode string + WhitelistID int64 + UserID int64 + Enabled bool + CreatedAtMs int64 + UpdatedAtMs int64 +} + // RegisterRiskConfig 是注册链路的风控开关集合。 // 当前只包含单设备最大账号数,后续增加注册频率或设备指纹策略时仍可复用同一行 App 配置。 type RegisterRiskConfig struct { diff --git a/services/user-service/internal/service/auth/risk_cache.go b/services/user-service/internal/service/auth/risk_cache.go index 16f57eb2..35a11b6b 100644 --- a/services/user-service/internal/service/auth/risk_cache.go +++ b/services/user-service/internal/service/auth/risk_cache.go @@ -83,6 +83,29 @@ func (c *RedisIPDecisionCache) SetIPWhitelist(ctx context.Context, appCode strin return c.client.Set(ctx, loginRiskIPWhitelistKey(appCode), raw, ttl).Err() } +func (c *RedisIPDecisionCache) GetUserWhitelist(ctx context.Context, appCode string) ([]int64, bool, error) { + raw, err := c.client.Get(ctx, loginRiskUserWhitelistKey(appCode)).Result() + if errors.Is(err, redis.Nil) { + return nil, false, nil + } + if err != nil { + return nil, false, err + } + var userIDs []int64 + if err := json.Unmarshal([]byte(raw), &userIDs); err != nil { + return nil, false, err + } + return userIDs, true, nil +} + +func (c *RedisIPDecisionCache) SetUserWhitelist(ctx context.Context, appCode string, userIDs []int64, ttl time.Duration) error { + raw, err := json.Marshal(userIDs) + if err != nil { + return err + } + return c.client.Set(ctx, loginRiskUserWhitelistKey(appCode), raw, ttl).Err() +} + func (c *RedisIPDecisionCache) GetRegisterRiskConfig(ctx context.Context, appCode string) (authdomain.RegisterRiskConfig, bool, error) { raw, err := c.client.Get(ctx, registerRiskConfigKey(appCode)).Result() if errors.Is(err, redis.Nil) { @@ -114,6 +137,10 @@ func loginRiskIPWhitelistKey(appCode string) string { return "login_risk:ip_whitelist:" + appcode.Normalize(appCode) } +func loginRiskUserWhitelistKey(appCode string) string { + return "login_risk:user_whitelist:" + appcode.Normalize(appCode) +} + func revokedSessionKey(appCode string, sessionID string) string { return "auth:revoked_session:" + appcode.Normalize(appCode) + ":" + strings.TrimSpace(sessionID) } diff --git a/services/user-service/internal/service/auth/risk_policy.go b/services/user-service/internal/service/auth/risk_policy.go index ee46ba1a..6bc44233 100644 --- a/services/user-service/internal/service/auth/risk_policy.go +++ b/services/user-service/internal/service/auth/risk_policy.go @@ -11,7 +11,19 @@ import ( authdomain "hyapp/services/user-service/internal/domain/auth" ) -const loginRiskIPWhitelistCacheTTL = 30 * time.Second +const ( + loginRiskIPWhitelistCacheTTL = 30 * time.Second + loginRiskUserWhitelistCacheTTL = 5 * time.Minute +) + +// LoginRiskWhitelistCacheSnapshot 是后台保存配置后主动刷新缓存的结果。 +// IP 白名单仍沿用原短 TTL;用户白名单按运营要求缓存 5 分钟,避免每个风控任务回源 MySQL。 +type LoginRiskWhitelistCacheSnapshot struct { + Refreshed bool + IPWhitelistCount int + UserWhitelistCount int + UserCacheExpiresAtMs int64 +} // ListLoginRiskBlockedCountries 返回当前 App 实际生效的登录地区风控词表。 // 静态配置和后台动态配置都下发给 App;App 可异步做本地风险提示,最终封禁仍以服务端 worker 为准。 @@ -79,6 +91,57 @@ func (s *Service) CheckLoginRiskIPWhitelisted(ctx context.Context, clientIP stri return false, nil } +// CheckLoginRiskUserWhitelisted 判断登录用户是否命中后台白名单。 +// 该白名单只在 user-service 登录后风控 worker 中使用;gateway 登录前没有可信 user_id,不能用它跳过入口快拦。 +func (s *Service) CheckLoginRiskUserWhitelisted(ctx context.Context, userID int64) (bool, error) { + if userID <= 0 { + return false, nil + } + userIDs, err := s.listLoginRiskUserWhitelist(ctx) + if err != nil { + return false, err + } + for _, item := range userIDs { + if item == userID { + return true, nil + } + } + return false, nil +} + +// RefreshLoginRiskWhitelistCache 从 MySQL 读取当前 App 白名单并立即写入 Redis。 +// 后台保存直接调用该方法,避免新增白名单后仍等 5 分钟 TTL 自然过期才生效。 +func (s *Service) RefreshLoginRiskWhitelistCache(ctx context.Context) (LoginRiskWhitelistCacheSnapshot, error) { + if s.authRepository == nil { + return LoginRiskWhitelistCacheSnapshot{}, nil + } + ips, err := s.loadLoginRiskIPWhitelist(ctx) + if err != nil { + return LoginRiskWhitelistCacheSnapshot{}, err + } + userIDs, err := s.loadLoginRiskUserWhitelist(ctx) + if err != nil { + return LoginRiskWhitelistCacheSnapshot{}, err + } + snapshot := LoginRiskWhitelistCacheSnapshot{ + IPWhitelistCount: len(ips), + UserWhitelistCount: len(userIDs), + UserCacheExpiresAtMs: s.now().Add(loginRiskUserWhitelistCacheTTL).UnixMilli(), + } + if s.ipDecisionCache == nil { + return snapshot, nil + } + // IP 和用户白名单共用一次刷新入口;任一缓存写失败都返回错误,让后台保存页能提示重试刷新。 + if err := s.ipDecisionCache.SetIPWhitelist(ctx, appcode.FromContext(ctx), ips, loginRiskIPWhitelistCacheTTL); err != nil { + return LoginRiskWhitelistCacheSnapshot{}, err + } + if err := s.ipDecisionCache.SetUserWhitelist(ctx, appcode.FromContext(ctx), userIDs, loginRiskUserWhitelistCacheTTL); err != nil { + return LoginRiskWhitelistCacheSnapshot{}, err + } + snapshot.Refreshed = true + return snapshot, nil +} + func (s *Service) listLoginRiskIPWhitelist(ctx context.Context) ([]string, error) { appCode := appcode.FromContext(ctx) if s.ipDecisionCache != nil { @@ -87,6 +150,37 @@ func (s *Service) listLoginRiskIPWhitelist(ctx context.Context) ([]string, error return normalizeLoginRiskIPList(ips), nil } } + ips, err := s.loadLoginRiskIPWhitelist(ctx) + if err != nil { + return nil, err + } + if s.ipDecisionCache != nil { + // 白名单读取结果按 App 整体缓存 30 秒;空列表也缓存,避免无配置时每次登录都回源 DB。 + _ = s.ipDecisionCache.SetIPWhitelist(ctx, appCode, ips, loginRiskIPWhitelistCacheTTL) + } + return ips, nil +} + +func (s *Service) listLoginRiskUserWhitelist(ctx context.Context) ([]int64, error) { + appCode := appcode.FromContext(ctx) + if s.ipDecisionCache != nil { + userIDs, ok, err := s.ipDecisionCache.GetUserWhitelist(ctx, appCode) + if err == nil && ok { + return normalizeLoginRiskUserIDList(userIDs), nil + } + } + userIDs, err := s.loadLoginRiskUserWhitelist(ctx) + if err != nil { + return nil, err + } + if s.ipDecisionCache != nil { + // 用户白名单按运营要求缓存 5 分钟;空列表也缓存,减少无配置时的任务回源。 + _ = s.ipDecisionCache.SetUserWhitelist(ctx, appCode, userIDs, loginRiskUserWhitelistCacheTTL) + } + return userIDs, nil +} + +func (s *Service) loadLoginRiskIPWhitelist(ctx context.Context) ([]string, error) { if s.authRepository == nil { return nil, nil } @@ -100,12 +194,24 @@ func (s *Service) listLoginRiskIPWhitelist(ctx context.Context) ([]string, error ips = append(ips, ip) } } - ips = normalizeLoginRiskIPList(ips) - if s.ipDecisionCache != nil { - // 白名单读取结果按 App 整体缓存 30 秒;空列表也缓存,避免无配置时每次登录都回源 DB。 - _ = s.ipDecisionCache.SetIPWhitelist(ctx, appCode, ips, loginRiskIPWhitelistCacheTTL) + return normalizeLoginRiskIPList(ips), nil +} + +func (s *Service) loadLoginRiskUserWhitelist(ctx context.Context) ([]int64, error) { + if s.authRepository == nil { + return nil, nil } - return ips, nil + items, err := s.authRepository.ListEnabledLoginRiskUserWhitelist(ctx) + if err != nil { + return nil, err + } + userIDs := make([]int64, 0, len(items)) + for _, item := range items { + if item.UserID > 0 { + userIDs = append(userIDs, item.UserID) + } + } + return normalizeLoginRiskUserIDList(userIDs), nil } func appendUniqueCountryBlock(blocks []authdomain.LoginRiskCountryBlock, seen map[string]struct{}, block authdomain.LoginRiskCountryBlock) []authdomain.LoginRiskCountryBlock { @@ -167,3 +273,22 @@ func normalizeLoginRiskIPList(raw []string) []string { sort.Strings(ips) return ips } + +func normalizeLoginRiskUserIDList(raw []int64) []int64 { + seen := make(map[int64]struct{}, len(raw)) + userIDs := make([]int64, 0, len(raw)) + for _, userID := range raw { + if userID <= 0 { + continue + } + if _, ok := seen[userID]; ok { + continue + } + seen[userID] = struct{}{} + userIDs = append(userIDs, userID) + } + sort.Slice(userIDs, func(i, j int) bool { + return userIDs[i] < userIDs[j] + }) + return userIDs +} diff --git a/services/user-service/internal/service/auth/risk_worker.go b/services/user-service/internal/service/auth/risk_worker.go index d7de306f..f7804a2d 100644 --- a/services/user-service/internal/service/auth/risk_worker.go +++ b/services/user-service/internal/service/auth/risk_worker.go @@ -71,6 +71,19 @@ func normalizeLoginIPRiskWorkerOptions(options LoginIPRiskWorkerOptions) LoginIP func (s *Service) processLoginIPRiskJob(ctx context.Context, job authdomain.LoginIPRiskJob) error { policy := s.loginRiskPolicy nowMs := s.now().UnixMilli() + userWhitelisted, err := s.CheckLoginRiskUserWhitelisted(ctx, job.UserID) + if err != nil { + logx.Warn(ctx, "login_user_whitelist_read_failed", slog.String("component", "user_auth_risk"), slog.String("job_id", job.JobID), slog.Int64("user_id", job.UserID), slog.String("error", err.Error())) + } + if userWhitelisted { + // 用户白名单是运营按真实 user_id 做的例外;命中后不再访问 GeoIP,也不写 IP 决策缓存,避免把用户级放行扩散到同 IP 其他账号。 + return s.applyLoginIPRiskDecision(ctx, job, IPDecision{ + Decision: authdomain.LoginIPRiskDecisionAllowed, + Source: "user_whitelist", + CheckedAtMs: nowMs, + ExpiresAtMs: nowMs + loginRiskUserWhitelistCacheTTL.Milliseconds(), + }, nil, authdomain.LoginIPRiskStatusCompleted) + } whitelisted, err := s.CheckLoginRiskIPWhitelisted(ctx, job.ClientIP) if err != nil { logx.Warn(ctx, "login_ip_whitelist_read_failed", slog.String("component", "user_auth_risk"), slog.String("job_id", job.JobID), slog.String("client_ip_hash", job.ClientIPHash), slog.String("error", err.Error())) diff --git a/services/user-service/internal/service/auth/service.go b/services/user-service/internal/service/auth/service.go index c518ff96..29610c6e 100644 --- a/services/user-service/internal/service/auth/service.go +++ b/services/user-service/internal/service/auth/service.go @@ -88,6 +88,8 @@ type AuthRepository interface { ListEnabledLoginRiskCountryBlocks(ctx context.Context) ([]authdomain.LoginRiskCountryBlock, error) // ListEnabledLoginRiskIPWhitelist 返回当前 App 的登录地区屏蔽 IP 白名单。 ListEnabledLoginRiskIPWhitelist(ctx context.Context) ([]authdomain.LoginRiskIPWhitelist, error) + // ListEnabledLoginRiskUserWhitelist 返回当前 App 的登录地区屏蔽用户白名单。 + ListEnabledLoginRiskUserWhitelist(ctx context.Context) ([]authdomain.LoginRiskUserWhitelist, error) // MarkLoginAuditBlocked 把登录成功后被异步风控撤销的审计行更新成阻断态。 MarkLoginAuditBlocked(ctx context.Context, requestID string, userID int64, countryCode string, blockReason string) error } @@ -177,6 +179,8 @@ type IPDecisionCache interface { SetRevokedSession(ctx context.Context, appCode string, sessionID string, reason string, ttl time.Duration) error GetIPWhitelist(ctx context.Context, appCode string) ([]string, bool, error) SetIPWhitelist(ctx context.Context, appCode string, ips []string, ttl time.Duration) error + GetUserWhitelist(ctx context.Context, appCode string) ([]int64, bool, error) + SetUserWhitelist(ctx context.Context, appCode string, userIDs []int64, ttl time.Duration) error } // RegisterRiskConfigCache 是注册风控配置的 Redis 快取边界。 diff --git a/services/user-service/internal/service/auth/service_test.go b/services/user-service/internal/service/auth/service_test.go index b7833035..828091cf 100644 --- a/services/user-service/internal/service/auth/service_test.go +++ b/services/user-service/internal/service/auth/service_test.go @@ -24,10 +24,18 @@ type memoryDecisionCache struct { ip map[string]authservice.IPDecision revoked map[string]string ips map[string][]string + users map[string][]int64 + userTTL map[string]time.Duration } func newMemoryDecisionCache() *memoryDecisionCache { - return &memoryDecisionCache{ip: make(map[string]authservice.IPDecision), revoked: make(map[string]string), ips: make(map[string][]string)} + return &memoryDecisionCache{ + ip: make(map[string]authservice.IPDecision), + revoked: make(map[string]string), + ips: make(map[string][]string), + users: make(map[string][]int64), + userTTL: make(map[string]time.Duration), + } } func (c *memoryDecisionCache) GetIPDecision(_ context.Context, appCode string, clientIPHash string) (authservice.IPDecision, bool, error) { @@ -65,6 +73,21 @@ func (c *memoryDecisionCache) SetIPWhitelist(_ context.Context, appCode string, return nil } +func (c *memoryDecisionCache) GetUserWhitelist(_ context.Context, appCode string) ([]int64, bool, error) { + c.mu.Lock() + defer c.mu.Unlock() + userIDs, ok := c.users[appCode] + return append([]int64(nil), userIDs...), ok, nil +} + +func (c *memoryDecisionCache) SetUserWhitelist(_ context.Context, appCode string, userIDs []int64, ttl time.Duration) error { + c.mu.Lock() + defer c.mu.Unlock() + c.users[appCode] = append([]int64(nil), userIDs...) + c.userTTL[appCode] = ttl + return nil +} + type sequenceIDGenerator struct { // values 是测试预设 user_id 序列。 values []int64 @@ -671,6 +694,81 @@ func TestLoginIPRiskIPWhitelistBypassesBlockedCountry(t *testing.T) { } } +func TestLoginIPRiskUserWhitelistBypassesBlockedCountryWithFiveMinuteCache(t *testing.T) { + ctx := context.Background() + repository := mysqltest.NewRepository(t) + seedCountry(t, repository, "CN") + now := time.UnixMilli(1000) + if _, err := repository.RawDB().ExecContext(ctx, ` + INSERT INTO login_risk_user_whitelist ( + app_code, user_id, enabled, created_at_ms, updated_at_ms + ) + VALUES ('lalu', 900025, 1, 1000, 1000) + `); err != nil { + t.Fatalf("seed user whitelist failed: %v", err) + } + cache := newMemoryDecisionCache() + authSvc := newAuthService(repository, &now, []int64{900025}, []string{"100025"}, + authservice.WithLoginRiskPolicy(authservice.LoginRiskPolicy{ + Enabled: true, + UnsupportedCountries: []string{"CN"}, + BlockedTTL: time.Hour, + AllowedTTL: time.Hour, + UnknownTTL: time.Minute, + ProviderTimeout: 50 * time.Millisecond, + ProviderNames: []string{"edge_country"}, + DenylistTTL: time.Hour, + }), + authservice.WithIPDecisionCache(cache), + authservice.WithIPGeoProviders(authservice.BuildIPGeoProviders([]string{"edge_country"})), + ) + + snapshot, err := authSvc.RefreshLoginRiskWhitelistCache(ctx) + if err != nil { + t.Fatalf("refresh whitelist cache failed: %v", err) + } + if !snapshot.Refreshed || snapshot.UserWhitelistCount != 1 || snapshot.UserCacheExpiresAtMs != now.Add(5*time.Minute).UnixMilli() { + t.Fatalf("user whitelist refresh snapshot mismatch: %+v", snapshot) + } + if got := cache.userTTL["lalu"]; got != 5*time.Minute { + t.Fatalf("user whitelist cache ttl = %v, want 5m", got) + } + + token, _, err := authSvc.LoginThirdParty(ctx, "wechat", "openid-risk-user-whitelist", thirdPartyRegistration("ios"), authservice.Meta{ + AppCode: "lalu", + RequestID: "req-risk-user-whitelist", + ClientIP: "203.0.113.81", + CountryByIP: "CN", + Platform: "ios", + }) + if err != nil { + t.Fatalf("LoginThirdParty failed: %v", err) + } + + now = time.UnixMilli(2000) + processed, err := authSvc.ProcessLoginIPRiskBatch(ctx, authservice.LoginIPRiskWorkerOptions{WorkerID: "risk-user-whitelist-test", LockTTL: time.Second, BatchSize: 10}) + if err != nil || processed != 1 { + t.Fatalf("risk worker mismatch: processed=%d err=%v", processed, err) + } + if _, err := authSvc.RefreshToken(ctx, token.RefreshToken, "dev-ios", authservice.Meta{AppCode: "lalu", RequestID: "req-refresh-user-whitelist"}); err != nil { + t.Fatalf("whitelisted user must keep refresh session active: %v", err) + } + if _, ok := cache.ip["lalu:"+authservice.HashLoginRiskIP("203.0.113.81")]; ok { + t.Fatalf("user whitelist must not write ordinary IP decision cache") + } + var decision string + if err := repository.RawDB().QueryRowContext(ctx, ` + SELECT decision + FROM login_ip_risk_jobs + WHERE app_code = 'lalu' AND request_id = 'req-risk-user-whitelist' + `).Scan(&decision); err != nil { + t.Fatalf("query risk job failed: %v", err) + } + if decision != "allowed" { + t.Fatalf("whitelisted user risk job decision mismatch: %s", decision) + } +} + func TestLoginIPRiskWorkerRevokesRotatedSameDeviceSessions(t *testing.T) { // 复现线上竞态:登录后风控任务尚未完成,客户端连续 refresh 轮换 session。 // blocked 决策落地时必须沿源 session 的同设备登录链路清掉后续 session,否则最新 access/refresh 仍能继续使用。 diff --git a/services/user-service/internal/storage/mysql/auth/risk.go b/services/user-service/internal/storage/mysql/auth/risk.go index abf7986d..6c78812f 100644 --- a/services/user-service/internal/storage/mysql/auth/risk.go +++ b/services/user-service/internal/storage/mysql/auth/risk.go @@ -161,6 +161,35 @@ func (r *Repository) ListEnabledLoginRiskIPWhitelist(ctx context.Context) ([]aut return items, nil } +func (r *Repository) ListEnabledLoginRiskUserWhitelist(ctx context.Context) ([]authdomain.LoginRiskUserWhitelist, error) { + rows, err := r.db.QueryContext(ctx, ` + SELECT app_code, whitelist_id, user_id, enabled, created_at_ms, updated_at_ms + FROM login_risk_user_whitelist + WHERE app_code = ? AND enabled = 1 + ORDER BY user_id ASC, whitelist_id ASC + `, appcode.FromContext(ctx)) + if err != nil { + return nil, err + } + defer rows.Close() + + items := make([]authdomain.LoginRiskUserWhitelist, 0) + for rows.Next() { + var item authdomain.LoginRiskUserWhitelist + if err := rows.Scan(&item.AppCode, &item.WhitelistID, &item.UserID, &item.Enabled, &item.CreatedAtMs, &item.UpdatedAtMs); err != nil { + return nil, err + } + if item.UserID <= 0 { + continue + } + items = append(items, item) + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + type loginIPRiskJobScanner interface { Scan(dest ...any) error } diff --git a/services/user-service/internal/testutil/mysqltest/mysqltest.go b/services/user-service/internal/testutil/mysqltest/mysqltest.go index fe90e44b..38edd349 100644 --- a/services/user-service/internal/testutil/mysqltest/mysqltest.go +++ b/services/user-service/internal/testutil/mysqltest/mysqltest.go @@ -434,6 +434,11 @@ func (r *Repository) ListEnabledLoginRiskIPWhitelist(ctx context.Context) ([]aut return r.Repository.AuthRepository().ListEnabledLoginRiskIPWhitelist(ctx) } +// ListEnabledLoginRiskUserWhitelist 让测试 wrapper 继续满足认证风控接口。 +func (r *Repository) ListEnabledLoginRiskUserWhitelist(ctx context.Context) ([]authdomain.LoginRiskUserWhitelist, error) { + return r.Repository.AuthRepository().ListEnabledLoginRiskUserWhitelist(ctx) +} + // MarkLoginAuditBlocked 让测试 wrapper 继续满足认证接口。 func (r *Repository) MarkLoginAuditBlocked(ctx context.Context, requestID string, userID int64, countryCode string, blockReason string) error { return r.Repository.AuthRepository().MarkLoginAuditBlocked(ctx, requestID, userID, countryCode, blockReason) diff --git a/services/user-service/internal/transport/grpc/server.go b/services/user-service/internal/transport/grpc/server.go index c3e8acb4..9b6cab33 100644 --- a/services/user-service/internal/transport/grpc/server.go +++ b/services/user-service/internal/transport/grpc/server.go @@ -264,6 +264,21 @@ func (s *Server) CheckLoginRiskIPWhitelist(ctx context.Context, req *userv1.Chec return &userv1.CheckLoginRiskIPWhitelistResponse{Whitelisted: whitelisted}, nil } +// RefreshLoginRiskWhitelistCache 让后台保存白名单后立刻重建 Redis 缓存,避免等待 TTL 自然过期。 +func (s *Server) RefreshLoginRiskWhitelistCache(ctx context.Context, req *userv1.RefreshLoginRiskWhitelistCacheRequest) (*userv1.RefreshLoginRiskWhitelistCacheResponse, error) { + ctx = contextWithApp(ctx, req.GetMeta()) + snapshot, err := s.authSvc.RefreshLoginRiskWhitelistCache(ctx) + if err != nil { + return nil, xerr.ToGRPCError(err) + } + return &userv1.RefreshLoginRiskWhitelistCacheResponse{ + Refreshed: snapshot.Refreshed, + IpWhitelistCount: int32(snapshot.IPWhitelistCount), + UserWhitelistCount: int32(snapshot.UserWhitelistCount), + UserCacheExpiresAtMs: snapshot.UserCacheExpiresAtMs, + }, nil +} + // GetRegisterRiskConfig 返回后台配置页和注册链路共用的设备注册上限。 func (s *Server) GetRegisterRiskConfig(ctx context.Context, req *userv1.GetRegisterRiskConfigRequest) (*userv1.GetRegisterRiskConfigResponse, error) { ctx = contextWithApp(ctx, req.GetMeta())