ip白名单

This commit is contained in:
zhx 2026-06-23 12:11:05 +08:00
parent c96777e405
commit 535ca29fa7
19 changed files with 784 additions and 151 deletions

View File

@ -1042,6 +1042,106 @@ func (x *RecordLoginBlockedResponse) GetRecorded() bool {
return false return false
} }
// CheckLoginRiskIPWhitelistRequest 查询入口 IP 是否命中后台白名单。
type CheckLoginRiskIPWhitelistRequest struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Meta *RequestMeta `protobuf:"bytes,1,opt,name=meta,proto3" json:"meta,omitempty"`
ClientIp string `protobuf:"bytes,2,opt,name=client_ip,json=clientIp,proto3" json:"client_ip,omitempty"`
}
func (x *CheckLoginRiskIPWhitelistRequest) Reset() {
*x = CheckLoginRiskIPWhitelistRequest{}
mi := &file_proto_user_v1_auth_proto_msgTypes[13]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *CheckLoginRiskIPWhitelistRequest) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*CheckLoginRiskIPWhitelistRequest) ProtoMessage() {}
func (x *CheckLoginRiskIPWhitelistRequest) ProtoReflect() protoreflect.Message {
mi := &file_proto_user_v1_auth_proto_msgTypes[13]
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 CheckLoginRiskIPWhitelistRequest.ProtoReflect.Descriptor instead.
func (*CheckLoginRiskIPWhitelistRequest) Descriptor() ([]byte, []int) {
return file_proto_user_v1_auth_proto_rawDescGZIP(), []int{13}
}
func (x *CheckLoginRiskIPWhitelistRequest) GetMeta() *RequestMeta {
if x != nil {
return x.Meta
}
return nil
}
func (x *CheckLoginRiskIPWhitelistRequest) GetClientIp() string {
if x != nil {
return x.ClientIp
}
return ""
}
// CheckLoginRiskIPWhitelistResponse 返回入口 IP 是否跳过登录地区屏蔽。
type CheckLoginRiskIPWhitelistResponse struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Whitelisted bool `protobuf:"varint,1,opt,name=whitelisted,proto3" json:"whitelisted,omitempty"`
}
func (x *CheckLoginRiskIPWhitelistResponse) Reset() {
*x = CheckLoginRiskIPWhitelistResponse{}
mi := &file_proto_user_v1_auth_proto_msgTypes[14]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *CheckLoginRiskIPWhitelistResponse) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*CheckLoginRiskIPWhitelistResponse) ProtoMessage() {}
func (x *CheckLoginRiskIPWhitelistResponse) ProtoReflect() protoreflect.Message {
mi := &file_proto_user_v1_auth_proto_msgTypes[14]
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 CheckLoginRiskIPWhitelistResponse.ProtoReflect.Descriptor instead.
func (*CheckLoginRiskIPWhitelistResponse) Descriptor() ([]byte, []int) {
return file_proto_user_v1_auth_proto_rawDescGZIP(), []int{14}
}
func (x *CheckLoginRiskIPWhitelistResponse) GetWhitelisted() bool {
if x != nil {
return x.Whitelisted
}
return false
}
// AppHeartbeatRequest 刷新当前登录会话的 App 在线心跳。 // AppHeartbeatRequest 刷新当前登录会话的 App 在线心跳。
type AppHeartbeatRequest struct { type AppHeartbeatRequest struct {
state protoimpl.MessageState state protoimpl.MessageState
@ -1055,7 +1155,7 @@ type AppHeartbeatRequest struct {
func (x *AppHeartbeatRequest) Reset() { func (x *AppHeartbeatRequest) Reset() {
*x = AppHeartbeatRequest{} *x = AppHeartbeatRequest{}
mi := &file_proto_user_v1_auth_proto_msgTypes[13] mi := &file_proto_user_v1_auth_proto_msgTypes[15]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi) ms.StoreMessageInfo(mi)
} }
@ -1067,7 +1167,7 @@ func (x *AppHeartbeatRequest) String() string {
func (*AppHeartbeatRequest) ProtoMessage() {} func (*AppHeartbeatRequest) ProtoMessage() {}
func (x *AppHeartbeatRequest) ProtoReflect() protoreflect.Message { func (x *AppHeartbeatRequest) ProtoReflect() protoreflect.Message {
mi := &file_proto_user_v1_auth_proto_msgTypes[13] mi := &file_proto_user_v1_auth_proto_msgTypes[15]
if x != nil { if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil { if ms.LoadMessageInfo() == nil {
@ -1080,7 +1180,7 @@ func (x *AppHeartbeatRequest) ProtoReflect() protoreflect.Message {
// Deprecated: Use AppHeartbeatRequest.ProtoReflect.Descriptor instead. // Deprecated: Use AppHeartbeatRequest.ProtoReflect.Descriptor instead.
func (*AppHeartbeatRequest) Descriptor() ([]byte, []int) { func (*AppHeartbeatRequest) Descriptor() ([]byte, []int) {
return file_proto_user_v1_auth_proto_rawDescGZIP(), []int{13} return file_proto_user_v1_auth_proto_rawDescGZIP(), []int{15}
} }
func (x *AppHeartbeatRequest) GetMeta() *RequestMeta { func (x *AppHeartbeatRequest) GetMeta() *RequestMeta {
@ -1118,7 +1218,7 @@ type AppHeartbeatResponse struct {
func (x *AppHeartbeatResponse) Reset() { func (x *AppHeartbeatResponse) Reset() {
*x = AppHeartbeatResponse{} *x = AppHeartbeatResponse{}
mi := &file_proto_user_v1_auth_proto_msgTypes[14] mi := &file_proto_user_v1_auth_proto_msgTypes[16]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi) ms.StoreMessageInfo(mi)
} }
@ -1130,7 +1230,7 @@ func (x *AppHeartbeatResponse) String() string {
func (*AppHeartbeatResponse) ProtoMessage() {} func (*AppHeartbeatResponse) ProtoMessage() {}
func (x *AppHeartbeatResponse) ProtoReflect() protoreflect.Message { func (x *AppHeartbeatResponse) ProtoReflect() protoreflect.Message {
mi := &file_proto_user_v1_auth_proto_msgTypes[14] mi := &file_proto_user_v1_auth_proto_msgTypes[16]
if x != nil { if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil { if ms.LoadMessageInfo() == nil {
@ -1143,7 +1243,7 @@ func (x *AppHeartbeatResponse) ProtoReflect() protoreflect.Message {
// Deprecated: Use AppHeartbeatResponse.ProtoReflect.Descriptor instead. // Deprecated: Use AppHeartbeatResponse.ProtoReflect.Descriptor instead.
func (*AppHeartbeatResponse) Descriptor() ([]byte, []int) { func (*AppHeartbeatResponse) Descriptor() ([]byte, []int) {
return file_proto_user_v1_auth_proto_rawDescGZIP(), []int{14} return file_proto_user_v1_auth_proto_rawDescGZIP(), []int{16}
} }
func (x *AppHeartbeatResponse) GetAccepted() bool { func (x *AppHeartbeatResponse) GetAccepted() bool {
@ -1335,75 +1435,95 @@ var file_proto_user_v1_auth_proto_rawDesc = []byte{
0x73, 0x6f, 0x6e, 0x22, 0x38, 0x0a, 0x1a, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x4c, 0x6f, 0x67, 0x73, 0x6f, 0x6e, 0x22, 0x38, 0x0a, 0x1a, 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, 0x69, 0x6e, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x65, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73,
0x65, 0x12, 0x1a, 0x0a, 0x08, 0x72, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x65, 0x64, 0x18, 0x01, 0x20, 0x65, 0x12, 0x1a, 0x0a, 0x08, 0x72, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x65, 0x64, 0x18, 0x01, 0x20,
0x01, 0x28, 0x08, 0x52, 0x08, 0x72, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x65, 0x64, 0x22, 0x7d, 0x0a, 0x01, 0x28, 0x08, 0x52, 0x08, 0x72, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x65, 0x64, 0x22, 0x6f, 0x0a,
0x13, 0x41, 0x70, 0x70, 0x48, 0x65, 0x61, 0x72, 0x74, 0x62, 0x65, 0x61, 0x74, 0x52, 0x65, 0x71, 0x20, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x52, 0x69, 0x73, 0x6b, 0x49,
0x75, 0x65, 0x73, 0x74, 0x12, 0x2e, 0x0a, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x50, 0x57, 0x68, 0x69, 0x74, 0x65, 0x6c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73,
0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x74, 0x12, 0x2e, 0x0a, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32,
0x76, 0x31, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x74, 0x61, 0x52, 0x04, 0x1a, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e,
0x6d, 0x65, 0x74, 0x61, 0x12, 0x17, 0x0a, 0x07, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x74, 0x61, 0x52, 0x04, 0x6d, 0x65, 0x74,
0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x1d, 0x0a, 0x61, 0x12, 0x1b, 0x0a, 0x09, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x5f, 0x69, 0x70, 0x18, 0x02,
0x0a, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x49, 0x70, 0x22, 0x45,
0x09, 0x52, 0x09, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x22, 0xb0, 0x01, 0x0a, 0x0a, 0x21, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x52, 0x69, 0x73, 0x6b,
0x14, 0x41, 0x70, 0x70, 0x48, 0x65, 0x61, 0x72, 0x74, 0x62, 0x65, 0x61, 0x74, 0x52, 0x65, 0x73, 0x49, 0x50, 0x57, 0x68, 0x69, 0x74, 0x65, 0x6c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f,
0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x1a, 0x0a, 0x08, 0x61, 0x63, 0x63, 0x65, 0x70, 0x74, 0x65, 0x6e, 0x73, 0x65, 0x12, 0x20, 0x0a, 0x0b, 0x77, 0x68, 0x69, 0x74, 0x65, 0x6c, 0x69, 0x73, 0x74,
0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x08, 0x61, 0x63, 0x63, 0x65, 0x70, 0x74, 0x65, 0x65, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0b, 0x77, 0x68, 0x69, 0x74, 0x65, 0x6c,
0x64, 0x12, 0x26, 0x0a, 0x0f, 0x68, 0x65, 0x61, 0x72, 0x74, 0x62, 0x65, 0x61, 0x74, 0x5f, 0x61, 0x69, 0x73, 0x74, 0x65, 0x64, 0x22, 0x7d, 0x0a, 0x13, 0x41, 0x70, 0x70, 0x48, 0x65, 0x61, 0x72,
0x74, 0x5f, 0x6d, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0d, 0x68, 0x65, 0x61, 0x72, 0x74, 0x62, 0x65, 0x61, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2e, 0x0a, 0x04,
0x74, 0x62, 0x65, 0x61, 0x74, 0x41, 0x74, 0x4d, 0x73, 0x12, 0x24, 0x0a, 0x0e, 0x73, 0x65, 0x72, 0x6d, 0x65, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x68, 0x79, 0x61,
0x76, 0x65, 0x72, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x5f, 0x6d, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65,
0x03, 0x52, 0x0c, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x54, 0x69, 0x6d, 0x65, 0x4d, 0x73, 0x12, 0x73, 0x74, 0x4d, 0x65, 0x74, 0x61, 0x52, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x12, 0x17, 0x0a, 0x07,
0x2e, 0x0a, 0x05, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x18, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x06, 0x75,
0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e,
0x75, 0x74, 0x68, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x52, 0x05, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x32, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x73, 0x65, 0x73, 0x73, 0x69,
0xdc, 0x05, 0x0a, 0x0b, 0x41, 0x75, 0x74, 0x68, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x6f, 0x6e, 0x49, 0x64, 0x22, 0xb0, 0x01, 0x0a, 0x14, 0x41, 0x70, 0x70, 0x48, 0x65, 0x61, 0x72,
0x51, 0x0a, 0x0d, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x50, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x74, 0x62, 0x65, 0x61, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x1a, 0x0a,
0x12, 0x23, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x08, 0x61, 0x63, 0x63, 0x65, 0x70, 0x74, 0x65, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52,
0x2e, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x50, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x52, 0x65, 0x08, 0x61, 0x63, 0x63, 0x65, 0x70, 0x74, 0x65, 0x64, 0x12, 0x26, 0x0a, 0x0f, 0x68, 0x65, 0x61,
0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1b, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x72, 0x74, 0x62, 0x65, 0x61, 0x74, 0x5f, 0x61, 0x74, 0x5f, 0x6d, 0x73, 0x18, 0x02, 0x20, 0x01,
0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x75, 0x74, 0x68, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x28, 0x03, 0x52, 0x0d, 0x68, 0x65, 0x61, 0x72, 0x74, 0x62, 0x65, 0x61, 0x74, 0x41, 0x74, 0x4d,
0x73, 0x65, 0x12, 0x55, 0x0a, 0x0f, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x54, 0x68, 0x69, 0x72, 0x64, 0x73, 0x12, 0x24, 0x0a, 0x0e, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x5f, 0x74, 0x69, 0x6d, 0x65,
0x50, 0x61, 0x72, 0x74, 0x79, 0x12, 0x25, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x5f, 0x6d, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x73, 0x65, 0x72, 0x76, 0x65,
0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x54, 0x68, 0x69, 0x72, 0x64, 0x72, 0x54, 0x69, 0x6d, 0x65, 0x4d, 0x73, 0x12, 0x2e, 0x0a, 0x05, 0x74, 0x6f, 0x6b, 0x65, 0x6e,
0x50, 0x61, 0x72, 0x74, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1b, 0x2e, 0x68, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75,
0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x75, 0x74, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x75, 0x74, 0x68, 0x54, 0x6f, 0x6b, 0x65, 0x6e,
0x68, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x54, 0x0a, 0x0b, 0x53, 0x65, 0x74, 0x52, 0x05, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x32, 0xdc, 0x06, 0x0a, 0x0b, 0x41, 0x75, 0x74, 0x68,
0x50, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x12, 0x21, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x51, 0x0a, 0x0d, 0x4c, 0x6f, 0x67, 0x69, 0x6e,
0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x65, 0x74, 0x50, 0x61, 0x73, 0x73, 0x50, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x12, 0x23, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70,
0x77, 0x6f, 0x72, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x22, 0x2e, 0x68, 0x79, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x50, 0x61,
0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x65, 0x74, 0x50, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1b, 0x2e,
0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x75,
0x69, 0x0a, 0x12, 0x51, 0x75, 0x69, 0x63, 0x6b, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x41, 0x63, 0x74, 0x68, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x55, 0x0a, 0x0f, 0x4c, 0x6f,
0x63, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x28, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x67, 0x69, 0x6e, 0x54, 0x68, 0x69, 0x72, 0x64, 0x50, 0x61, 0x72, 0x74, 0x79, 0x12, 0x25, 0x2e,
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, 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, 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, 0x67, 0x69, 0x6e, 0x54, 0x68, 0x69, 0x72, 0x64, 0x50, 0x61, 0x72, 0x74, 0x79, 0x52, 0x65, 0x71,
0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x6f, 0x67, 0x6f, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1b, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65,
0x75, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x69, 0x0a, 0x12, 0x52, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x75, 0x74, 0x68, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73,
0x63, 0x6f, 0x72, 0x64, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x65, 0x64, 0x65, 0x12, 0x54, 0x0a, 0x0b, 0x53, 0x65, 0x74, 0x50, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64,
0x12, 0x28, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x12, 0x21, 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, 0x2e, 0x53, 0x65, 0x74, 0x50, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x52, 0x65, 0x71, 0x75,
0x6b, 0x65, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x29, 0x2e, 0x68, 0x79, 0x61, 0x65, 0x73, 0x74, 0x1a, 0x22, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72,
0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x65, 0x74, 0x50, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x52,
0x64, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x65, 0x64, 0x52, 0x65, 0x73, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x69, 0x0a, 0x12, 0x51, 0x75, 0x69, 0x63, 0x6b,
0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x57, 0x0a, 0x0c, 0x41, 0x70, 0x70, 0x48, 0x65, 0x61, 0x72, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x28, 0x2e,
0x74, 0x62, 0x65, 0x61, 0x74, 0x12, 0x22, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x51, 0x75,
0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x70, 0x70, 0x48, 0x65, 0x61, 0x72, 0x74, 0x62, 0x65, 0x69, 0x63, 0x6b, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74,
0x61, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x23, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x29, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e,
0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x70, 0x70, 0x48, 0x65, 0x61, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x51, 0x75, 0x69, 0x63, 0x6b, 0x43, 0x72, 0x65,
0x72, 0x74, 0x62, 0x65, 0x61, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x42, 0x26, 0x61, 0x74, 0x65, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e,
0x5a, 0x24, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x2f, 0x61, 0x70, 0x73, 0x65, 0x12, 0x57, 0x0a, 0x0c, 0x52, 0x65, 0x66, 0x72, 0x65, 0x73, 0x68, 0x54, 0x6f, 0x6b,
0x69, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x75, 0x73, 0x65, 0x72, 0x2f, 0x76, 0x31, 0x3b, 0x65, 0x6e, 0x12, 0x22, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e,
0x75, 0x73, 0x65, 0x72, 0x76, 0x31, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, 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, 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, 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, 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 ( var (
@ -1418,60 +1538,65 @@ func file_proto_user_v1_auth_proto_rawDescGZIP() []byte {
return file_proto_user_v1_auth_proto_rawDescData return file_proto_user_v1_auth_proto_rawDescData
} }
var file_proto_user_v1_auth_proto_msgTypes = make([]protoimpl.MessageInfo, 15) var file_proto_user_v1_auth_proto_msgTypes = make([]protoimpl.MessageInfo, 17)
var file_proto_user_v1_auth_proto_goTypes = []any{ var file_proto_user_v1_auth_proto_goTypes = []any{
(*LoginPasswordRequest)(nil), // 0: hyapp.user.v1.LoginPasswordRequest (*LoginPasswordRequest)(nil), // 0: hyapp.user.v1.LoginPasswordRequest
(*LoginThirdPartyRequest)(nil), // 1: hyapp.user.v1.LoginThirdPartyRequest (*LoginThirdPartyRequest)(nil), // 1: hyapp.user.v1.LoginThirdPartyRequest
(*AuthResponse)(nil), // 2: hyapp.user.v1.AuthResponse (*AuthResponse)(nil), // 2: hyapp.user.v1.AuthResponse
(*SetPasswordRequest)(nil), // 3: hyapp.user.v1.SetPasswordRequest (*SetPasswordRequest)(nil), // 3: hyapp.user.v1.SetPasswordRequest
(*SetPasswordResponse)(nil), // 4: hyapp.user.v1.SetPasswordResponse (*SetPasswordResponse)(nil), // 4: hyapp.user.v1.SetPasswordResponse
(*QuickCreateAccountRequest)(nil), // 5: hyapp.user.v1.QuickCreateAccountRequest (*QuickCreateAccountRequest)(nil), // 5: hyapp.user.v1.QuickCreateAccountRequest
(*QuickCreateAccountResponse)(nil), // 6: hyapp.user.v1.QuickCreateAccountResponse (*QuickCreateAccountResponse)(nil), // 6: hyapp.user.v1.QuickCreateAccountResponse
(*RefreshTokenRequest)(nil), // 7: hyapp.user.v1.RefreshTokenRequest (*RefreshTokenRequest)(nil), // 7: hyapp.user.v1.RefreshTokenRequest
(*RefreshTokenResponse)(nil), // 8: hyapp.user.v1.RefreshTokenResponse (*RefreshTokenResponse)(nil), // 8: hyapp.user.v1.RefreshTokenResponse
(*LogoutRequest)(nil), // 9: hyapp.user.v1.LogoutRequest (*LogoutRequest)(nil), // 9: hyapp.user.v1.LogoutRequest
(*LogoutResponse)(nil), // 10: hyapp.user.v1.LogoutResponse (*LogoutResponse)(nil), // 10: hyapp.user.v1.LogoutResponse
(*RecordLoginBlockedRequest)(nil), // 11: hyapp.user.v1.RecordLoginBlockedRequest (*RecordLoginBlockedRequest)(nil), // 11: hyapp.user.v1.RecordLoginBlockedRequest
(*RecordLoginBlockedResponse)(nil), // 12: hyapp.user.v1.RecordLoginBlockedResponse (*RecordLoginBlockedResponse)(nil), // 12: hyapp.user.v1.RecordLoginBlockedResponse
(*AppHeartbeatRequest)(nil), // 13: hyapp.user.v1.AppHeartbeatRequest (*CheckLoginRiskIPWhitelistRequest)(nil), // 13: hyapp.user.v1.CheckLoginRiskIPWhitelistRequest
(*AppHeartbeatResponse)(nil), // 14: hyapp.user.v1.AppHeartbeatResponse (*CheckLoginRiskIPWhitelistResponse)(nil), // 14: hyapp.user.v1.CheckLoginRiskIPWhitelistResponse
(*RequestMeta)(nil), // 15: hyapp.user.v1.RequestMeta (*AppHeartbeatRequest)(nil), // 15: hyapp.user.v1.AppHeartbeatRequest
(*AuthToken)(nil), // 16: hyapp.user.v1.AuthToken (*AppHeartbeatResponse)(nil), // 16: hyapp.user.v1.AppHeartbeatResponse
(*RequestMeta)(nil), // 17: hyapp.user.v1.RequestMeta
(*AuthToken)(nil), // 18: hyapp.user.v1.AuthToken
} }
var file_proto_user_v1_auth_proto_depIdxs = []int32{ var file_proto_user_v1_auth_proto_depIdxs = []int32{
15, // 0: hyapp.user.v1.LoginPasswordRequest.meta:type_name -> hyapp.user.v1.RequestMeta 17, // 0: hyapp.user.v1.LoginPasswordRequest.meta:type_name -> hyapp.user.v1.RequestMeta
15, // 1: hyapp.user.v1.LoginThirdPartyRequest.meta:type_name -> hyapp.user.v1.RequestMeta 17, // 1: hyapp.user.v1.LoginThirdPartyRequest.meta:type_name -> hyapp.user.v1.RequestMeta
16, // 2: hyapp.user.v1.AuthResponse.token:type_name -> hyapp.user.v1.AuthToken 18, // 2: hyapp.user.v1.AuthResponse.token:type_name -> hyapp.user.v1.AuthToken
15, // 3: hyapp.user.v1.SetPasswordRequest.meta:type_name -> hyapp.user.v1.RequestMeta 17, // 3: hyapp.user.v1.SetPasswordRequest.meta:type_name -> hyapp.user.v1.RequestMeta
15, // 4: hyapp.user.v1.QuickCreateAccountRequest.meta:type_name -> hyapp.user.v1.RequestMeta 17, // 4: hyapp.user.v1.QuickCreateAccountRequest.meta:type_name -> hyapp.user.v1.RequestMeta
16, // 5: hyapp.user.v1.QuickCreateAccountResponse.token:type_name -> hyapp.user.v1.AuthToken 18, // 5: hyapp.user.v1.QuickCreateAccountResponse.token:type_name -> hyapp.user.v1.AuthToken
15, // 6: hyapp.user.v1.RefreshTokenRequest.meta:type_name -> hyapp.user.v1.RequestMeta 17, // 6: hyapp.user.v1.RefreshTokenRequest.meta:type_name -> hyapp.user.v1.RequestMeta
16, // 7: hyapp.user.v1.RefreshTokenResponse.token:type_name -> hyapp.user.v1.AuthToken 18, // 7: hyapp.user.v1.RefreshTokenResponse.token:type_name -> hyapp.user.v1.AuthToken
15, // 8: hyapp.user.v1.LogoutRequest.meta:type_name -> hyapp.user.v1.RequestMeta 17, // 8: hyapp.user.v1.LogoutRequest.meta:type_name -> hyapp.user.v1.RequestMeta
15, // 9: hyapp.user.v1.RecordLoginBlockedRequest.meta:type_name -> hyapp.user.v1.RequestMeta 17, // 9: hyapp.user.v1.RecordLoginBlockedRequest.meta:type_name -> hyapp.user.v1.RequestMeta
15, // 10: hyapp.user.v1.AppHeartbeatRequest.meta:type_name -> hyapp.user.v1.RequestMeta 17, // 10: hyapp.user.v1.CheckLoginRiskIPWhitelistRequest.meta:type_name -> hyapp.user.v1.RequestMeta
16, // 11: hyapp.user.v1.AppHeartbeatResponse.token:type_name -> hyapp.user.v1.AuthToken 17, // 11: hyapp.user.v1.AppHeartbeatRequest.meta:type_name -> hyapp.user.v1.RequestMeta
0, // 12: hyapp.user.v1.AuthService.LoginPassword:input_type -> hyapp.user.v1.LoginPasswordRequest 18, // 12: hyapp.user.v1.AppHeartbeatResponse.token:type_name -> hyapp.user.v1.AuthToken
1, // 13: hyapp.user.v1.AuthService.LoginThirdParty:input_type -> hyapp.user.v1.LoginThirdPartyRequest 0, // 13: hyapp.user.v1.AuthService.LoginPassword:input_type -> hyapp.user.v1.LoginPasswordRequest
3, // 14: hyapp.user.v1.AuthService.SetPassword:input_type -> hyapp.user.v1.SetPasswordRequest 1, // 14: hyapp.user.v1.AuthService.LoginThirdParty:input_type -> hyapp.user.v1.LoginThirdPartyRequest
5, // 15: hyapp.user.v1.AuthService.QuickCreateAccount:input_type -> hyapp.user.v1.QuickCreateAccountRequest 3, // 15: hyapp.user.v1.AuthService.SetPassword:input_type -> hyapp.user.v1.SetPasswordRequest
7, // 16: hyapp.user.v1.AuthService.RefreshToken:input_type -> hyapp.user.v1.RefreshTokenRequest 5, // 16: hyapp.user.v1.AuthService.QuickCreateAccount:input_type -> hyapp.user.v1.QuickCreateAccountRequest
9, // 17: hyapp.user.v1.AuthService.Logout:input_type -> hyapp.user.v1.LogoutRequest 7, // 17: hyapp.user.v1.AuthService.RefreshToken:input_type -> hyapp.user.v1.RefreshTokenRequest
11, // 18: hyapp.user.v1.AuthService.RecordLoginBlocked:input_type -> hyapp.user.v1.RecordLoginBlockedRequest 9, // 18: hyapp.user.v1.AuthService.Logout:input_type -> hyapp.user.v1.LogoutRequest
13, // 19: hyapp.user.v1.AuthService.AppHeartbeat:input_type -> hyapp.user.v1.AppHeartbeatRequest 11, // 19: hyapp.user.v1.AuthService.RecordLoginBlocked:input_type -> hyapp.user.v1.RecordLoginBlockedRequest
2, // 20: hyapp.user.v1.AuthService.LoginPassword:output_type -> hyapp.user.v1.AuthResponse 13, // 20: hyapp.user.v1.AuthService.CheckLoginRiskIPWhitelist:input_type -> hyapp.user.v1.CheckLoginRiskIPWhitelistRequest
2, // 21: hyapp.user.v1.AuthService.LoginThirdParty:output_type -> hyapp.user.v1.AuthResponse 15, // 21: hyapp.user.v1.AuthService.AppHeartbeat:input_type -> hyapp.user.v1.AppHeartbeatRequest
4, // 22: hyapp.user.v1.AuthService.SetPassword:output_type -> hyapp.user.v1.SetPasswordResponse 2, // 22: hyapp.user.v1.AuthService.LoginPassword:output_type -> hyapp.user.v1.AuthResponse
6, // 23: hyapp.user.v1.AuthService.QuickCreateAccount:output_type -> hyapp.user.v1.QuickCreateAccountResponse 2, // 23: hyapp.user.v1.AuthService.LoginThirdParty:output_type -> hyapp.user.v1.AuthResponse
8, // 24: hyapp.user.v1.AuthService.RefreshToken:output_type -> hyapp.user.v1.RefreshTokenResponse 4, // 24: hyapp.user.v1.AuthService.SetPassword:output_type -> hyapp.user.v1.SetPasswordResponse
10, // 25: hyapp.user.v1.AuthService.Logout:output_type -> hyapp.user.v1.LogoutResponse 6, // 25: hyapp.user.v1.AuthService.QuickCreateAccount:output_type -> hyapp.user.v1.QuickCreateAccountResponse
12, // 26: hyapp.user.v1.AuthService.RecordLoginBlocked:output_type -> hyapp.user.v1.RecordLoginBlockedResponse 8, // 26: hyapp.user.v1.AuthService.RefreshToken:output_type -> hyapp.user.v1.RefreshTokenResponse
14, // 27: hyapp.user.v1.AuthService.AppHeartbeat:output_type -> hyapp.user.v1.AppHeartbeatResponse 10, // 27: hyapp.user.v1.AuthService.Logout:output_type -> hyapp.user.v1.LogoutResponse
20, // [20:28] is the sub-list for method output_type 12, // 28: hyapp.user.v1.AuthService.RecordLoginBlocked:output_type -> hyapp.user.v1.RecordLoginBlockedResponse
12, // [12:20] is the sub-list for method input_type 14, // 29: hyapp.user.v1.AuthService.CheckLoginRiskIPWhitelist:output_type -> hyapp.user.v1.CheckLoginRiskIPWhitelistResponse
12, // [12:12] is the sub-list for extension type_name 16, // 30: hyapp.user.v1.AuthService.AppHeartbeat:output_type -> hyapp.user.v1.AppHeartbeatResponse
12, // [12:12] is the sub-list for extension extendee 22, // [22:31] is the sub-list for method output_type
0, // [0:12] is the sub-list for field type_name 13, // [13:22] is the sub-list for method input_type
13, // [13:13] is the sub-list for extension type_name
13, // [13:13] is the sub-list for extension extendee
0, // [0:13] is the sub-list for field type_name
} }
func init() { file_proto_user_v1_auth_proto_init() } func init() { file_proto_user_v1_auth_proto_init() }
@ -1486,7 +1611,7 @@ func file_proto_user_v1_auth_proto_init() {
GoPackagePath: reflect.TypeOf(x{}).PkgPath(), GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
RawDescriptor: file_proto_user_v1_auth_proto_rawDesc, RawDescriptor: file_proto_user_v1_auth_proto_rawDesc,
NumEnums: 0, NumEnums: 0,
NumMessages: 15, NumMessages: 17,
NumExtensions: 0, NumExtensions: 0,
NumServices: 1, NumServices: 1,
}, },

View File

@ -124,6 +124,17 @@ message RecordLoginBlockedResponse {
bool recorded = 1; bool recorded = 1;
} }
// CheckLoginRiskIPWhitelistRequest IP
message CheckLoginRiskIPWhitelistRequest {
RequestMeta meta = 1;
string client_ip = 2;
}
// CheckLoginRiskIPWhitelistResponse IP
message CheckLoginRiskIPWhitelistResponse {
bool whitelisted = 1;
}
// AppHeartbeatRequest App 线 // AppHeartbeatRequest App 线
message AppHeartbeatRequest { message AppHeartbeatRequest {
RequestMeta meta = 1; RequestMeta meta = 1;
@ -148,5 +159,6 @@ service AuthService {
rpc RefreshToken(RefreshTokenRequest) returns (RefreshTokenResponse); rpc RefreshToken(RefreshTokenRequest) returns (RefreshTokenResponse);
rpc Logout(LogoutRequest) returns (LogoutResponse); rpc Logout(LogoutRequest) returns (LogoutResponse);
rpc RecordLoginBlocked(RecordLoginBlockedRequest) returns (RecordLoginBlockedResponse); rpc RecordLoginBlocked(RecordLoginBlockedRequest) returns (RecordLoginBlockedResponse);
rpc CheckLoginRiskIPWhitelist(CheckLoginRiskIPWhitelistRequest) returns (CheckLoginRiskIPWhitelistResponse);
rpc AppHeartbeat(AppHeartbeatRequest) returns (AppHeartbeatResponse); rpc AppHeartbeat(AppHeartbeatRequest) returns (AppHeartbeatResponse);
} }

View File

@ -19,14 +19,15 @@ import (
const _ = grpc.SupportPackageIsVersion9 const _ = grpc.SupportPackageIsVersion9
const ( const (
AuthService_LoginPassword_FullMethodName = "/hyapp.user.v1.AuthService/LoginPassword" AuthService_LoginPassword_FullMethodName = "/hyapp.user.v1.AuthService/LoginPassword"
AuthService_LoginThirdParty_FullMethodName = "/hyapp.user.v1.AuthService/LoginThirdParty" AuthService_LoginThirdParty_FullMethodName = "/hyapp.user.v1.AuthService/LoginThirdParty"
AuthService_SetPassword_FullMethodName = "/hyapp.user.v1.AuthService/SetPassword" AuthService_SetPassword_FullMethodName = "/hyapp.user.v1.AuthService/SetPassword"
AuthService_QuickCreateAccount_FullMethodName = "/hyapp.user.v1.AuthService/QuickCreateAccount" AuthService_QuickCreateAccount_FullMethodName = "/hyapp.user.v1.AuthService/QuickCreateAccount"
AuthService_RefreshToken_FullMethodName = "/hyapp.user.v1.AuthService/RefreshToken" AuthService_RefreshToken_FullMethodName = "/hyapp.user.v1.AuthService/RefreshToken"
AuthService_Logout_FullMethodName = "/hyapp.user.v1.AuthService/Logout" AuthService_Logout_FullMethodName = "/hyapp.user.v1.AuthService/Logout"
AuthService_RecordLoginBlocked_FullMethodName = "/hyapp.user.v1.AuthService/RecordLoginBlocked" AuthService_RecordLoginBlocked_FullMethodName = "/hyapp.user.v1.AuthService/RecordLoginBlocked"
AuthService_AppHeartbeat_FullMethodName = "/hyapp.user.v1.AuthService/AppHeartbeat" AuthService_CheckLoginRiskIPWhitelist_FullMethodName = "/hyapp.user.v1.AuthService/CheckLoginRiskIPWhitelist"
AuthService_AppHeartbeat_FullMethodName = "/hyapp.user.v1.AuthService/AppHeartbeat"
) )
// AuthServiceClient is the client API for AuthService service. // AuthServiceClient is the client API for AuthService service.
@ -42,6 +43,7 @@ type AuthServiceClient interface {
RefreshToken(ctx context.Context, in *RefreshTokenRequest, opts ...grpc.CallOption) (*RefreshTokenResponse, error) RefreshToken(ctx context.Context, in *RefreshTokenRequest, opts ...grpc.CallOption) (*RefreshTokenResponse, error)
Logout(ctx context.Context, in *LogoutRequest, opts ...grpc.CallOption) (*LogoutResponse, error) Logout(ctx context.Context, in *LogoutRequest, opts ...grpc.CallOption) (*LogoutResponse, error)
RecordLoginBlocked(ctx context.Context, in *RecordLoginBlockedRequest, opts ...grpc.CallOption) (*RecordLoginBlockedResponse, error) RecordLoginBlocked(ctx context.Context, in *RecordLoginBlockedRequest, opts ...grpc.CallOption) (*RecordLoginBlockedResponse, error)
CheckLoginRiskIPWhitelist(ctx context.Context, in *CheckLoginRiskIPWhitelistRequest, opts ...grpc.CallOption) (*CheckLoginRiskIPWhitelistResponse, error)
AppHeartbeat(ctx context.Context, in *AppHeartbeatRequest, opts ...grpc.CallOption) (*AppHeartbeatResponse, error) AppHeartbeat(ctx context.Context, in *AppHeartbeatRequest, opts ...grpc.CallOption) (*AppHeartbeatResponse, error)
} }
@ -123,6 +125,16 @@ func (c *authServiceClient) RecordLoginBlocked(ctx context.Context, in *RecordLo
return out, nil return out, nil
} }
func (c *authServiceClient) CheckLoginRiskIPWhitelist(ctx context.Context, in *CheckLoginRiskIPWhitelistRequest, opts ...grpc.CallOption) (*CheckLoginRiskIPWhitelistResponse, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(CheckLoginRiskIPWhitelistResponse)
err := c.cc.Invoke(ctx, AuthService_CheckLoginRiskIPWhitelist_FullMethodName, in, out, cOpts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *authServiceClient) AppHeartbeat(ctx context.Context, in *AppHeartbeatRequest, opts ...grpc.CallOption) (*AppHeartbeatResponse, error) { func (c *authServiceClient) AppHeartbeat(ctx context.Context, in *AppHeartbeatRequest, opts ...grpc.CallOption) (*AppHeartbeatResponse, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(AppHeartbeatResponse) out := new(AppHeartbeatResponse)
@ -146,6 +158,7 @@ type AuthServiceServer interface {
RefreshToken(context.Context, *RefreshTokenRequest) (*RefreshTokenResponse, error) RefreshToken(context.Context, *RefreshTokenRequest) (*RefreshTokenResponse, error)
Logout(context.Context, *LogoutRequest) (*LogoutResponse, error) Logout(context.Context, *LogoutRequest) (*LogoutResponse, error)
RecordLoginBlocked(context.Context, *RecordLoginBlockedRequest) (*RecordLoginBlockedResponse, error) RecordLoginBlocked(context.Context, *RecordLoginBlockedRequest) (*RecordLoginBlockedResponse, error)
CheckLoginRiskIPWhitelist(context.Context, *CheckLoginRiskIPWhitelistRequest) (*CheckLoginRiskIPWhitelistResponse, error)
AppHeartbeat(context.Context, *AppHeartbeatRequest) (*AppHeartbeatResponse, error) AppHeartbeat(context.Context, *AppHeartbeatRequest) (*AppHeartbeatResponse, error)
mustEmbedUnimplementedAuthServiceServer() mustEmbedUnimplementedAuthServiceServer()
} }
@ -178,6 +191,9 @@ func (UnimplementedAuthServiceServer) Logout(context.Context, *LogoutRequest) (*
func (UnimplementedAuthServiceServer) RecordLoginBlocked(context.Context, *RecordLoginBlockedRequest) (*RecordLoginBlockedResponse, error) { func (UnimplementedAuthServiceServer) RecordLoginBlocked(context.Context, *RecordLoginBlockedRequest) (*RecordLoginBlockedResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method RecordLoginBlocked not implemented") return nil, status.Errorf(codes.Unimplemented, "method RecordLoginBlocked not implemented")
} }
func (UnimplementedAuthServiceServer) CheckLoginRiskIPWhitelist(context.Context, *CheckLoginRiskIPWhitelistRequest) (*CheckLoginRiskIPWhitelistResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method CheckLoginRiskIPWhitelist not implemented")
}
func (UnimplementedAuthServiceServer) AppHeartbeat(context.Context, *AppHeartbeatRequest) (*AppHeartbeatResponse, error) { func (UnimplementedAuthServiceServer) AppHeartbeat(context.Context, *AppHeartbeatRequest) (*AppHeartbeatResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method AppHeartbeat not implemented") return nil, status.Errorf(codes.Unimplemented, "method AppHeartbeat not implemented")
} }
@ -328,6 +344,24 @@ func _AuthService_RecordLoginBlocked_Handler(srv interface{}, ctx context.Contex
return interceptor(ctx, in, info, handler) return interceptor(ctx, in, info, handler)
} }
func _AuthService_CheckLoginRiskIPWhitelist_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(CheckLoginRiskIPWhitelistRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(AuthServiceServer).CheckLoginRiskIPWhitelist(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: AuthService_CheckLoginRiskIPWhitelist_FullMethodName,
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(AuthServiceServer).CheckLoginRiskIPWhitelist(ctx, req.(*CheckLoginRiskIPWhitelistRequest))
}
return interceptor(ctx, in, info, handler)
}
func _AuthService_AppHeartbeat_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { func _AuthService_AppHeartbeat_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(AppHeartbeatRequest) in := new(AppHeartbeatRequest)
if err := dec(in); err != nil { if err := dec(in); err != nil {
@ -381,6 +415,10 @@ var AuthService_ServiceDesc = grpc.ServiceDesc{
MethodName: "RecordLoginBlocked", MethodName: "RecordLoginBlocked",
Handler: _AuthService_RecordLoginBlocked_Handler, Handler: _AuthService_RecordLoginBlocked_Handler,
}, },
{
MethodName: "CheckLoginRiskIPWhitelist",
Handler: _AuthService_CheckLoginRiskIPWhitelist_Handler,
},
{ {
MethodName: "AppHeartbeat", MethodName: "AppHeartbeat",
Handler: _AuthService_AppHeartbeat_Handler, Handler: _AuthService_AppHeartbeat_Handler,

View File

@ -0,0 +1,14 @@
CREATE TABLE IF NOT EXISTS login_risk_ip_whitelist (
app_code VARCHAR(32) NOT NULL COMMENT '应用编码,用于多租户隔离',
whitelist_id BIGINT NOT NULL AUTO_INCREMENT COMMENT '白名单 ID',
ip_address VARCHAR(64) NOT NULL COMMENT '允许跳过登录地区屏蔽的客户端 IP',
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_ip_whitelist_ip (app_code, ip_address),
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 白名单表';

View File

@ -18,7 +18,8 @@ type Handler struct {
} }
type replaceRequest struct { type replaceRequest struct {
Keywords *[]string `json:"keywords"` Keywords *[]string `json:"keywords"`
WhitelistIPs *[]string `json:"whitelist_ips"`
} }
func New(userDB *sql.DB, audit shared.OperationLogger) *Handler { func New(userDB *sql.DB, audit shared.OperationLogger) *Handler {
@ -32,7 +33,7 @@ func (h *Handler) ListRegionBlocks(c *gin.Context) {
response.ServerError(c, "获取地区屏蔽配置失败") response.ServerError(c, "获取地区屏蔽配置失败")
return return
} }
response.OK(c, gin.H{"items": items, "total": len(items)}) response.OK(c, items)
} }
// ReplaceRegionBlocks 保存整页地区屏蔽词配置;空数组表示清空屏蔽词。 // ReplaceRegionBlocks 保存整页地区屏蔽词配置;空数组表示清空屏蔽词。
@ -42,7 +43,7 @@ func (h *Handler) ReplaceRegionBlocks(c *gin.Context) {
response.BadRequest(c, "地区屏蔽词参数不正确") response.BadRequest(c, "地区屏蔽词参数不正确")
return return
} }
items, err := h.service.Replace(c.Request.Context(), int64(shared.ActorFromContext(c).UserID), *req.Keywords) items, err := h.service.Replace(c.Request.Context(), int64(shared.ActorFromContext(c).UserID), *req.Keywords, req.WhitelistIPs)
if err != nil { if err != nil {
if errors.Is(err, errInvalidPayload) { if errors.Is(err, errInvalidPayload) {
response.BadRequest(c, "地区屏蔽词参数不正确") response.BadRequest(c, "地区屏蔽词参数不正确")
@ -51,6 +52,6 @@ func (h *Handler) ReplaceRegionBlocks(c *gin.Context) {
response.ServerError(c, "保存地区屏蔽配置失败") response.ServerError(c, "保存地区屏蔽配置失败")
return return
} }
shared.OperationLogWithResourceID(c, h.audit, "replace-region-blocks", "login_risk_country_blocks", middleware.CurrentRequestID(c), "success", fmt.Sprintf("keywords=%d", len(items))) 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)))
response.OK(c, gin.H{"items": items, "total": len(items)}) response.OK(c, items)
} }

View File

@ -4,6 +4,7 @@ import (
"context" "context"
"database/sql" "database/sql"
"errors" "errors"
"net/netip"
"regexp" "regexp"
"strings" "strings"
"time" "time"
@ -15,6 +16,7 @@ import (
const ( const (
maxRegionBlockKeywords = 200 maxRegionBlockKeywords = 200
maxRegionBlockKeywordLength = 128 maxRegionBlockKeywordLength = 128
maxIPWhitelistItems = 500
) )
var ( var (
@ -35,6 +37,21 @@ type RegionBlock struct {
UpdatedAtMS int64 `json:"updated_at_ms"` UpdatedAtMS int64 `json:"updated_at_ms"`
} }
type IPWhitelistItem struct {
WhitelistID int64 `json:"whitelist_id"`
IPAddress string `json:"ip_address"`
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"`
}
type normalizedKeyword struct { type normalizedKeyword struct {
Keyword string Keyword string
CountryCode string CountryCode string
@ -48,28 +65,44 @@ func NewService(userDB *sql.DB) *Service {
return &Service{userDB: userDB} return &Service{userDB: userDB}
} }
// List 返回当前 App 已启用的登录地区屏蔽词 // List 返回当前 App 已启用的登录地区屏蔽词和 IP 白名单
// 表属于 user-service 登录风控事实admin-server 只作为后台维护入口直接写 user DB。 // 表属于 user-service 登录风控事实admin-server 只作为后台维护入口直接写 user DB。
func (s *Service) List(ctx context.Context) ([]RegionBlock, error) { func (s *Service) List(ctx context.Context) (Config, error) {
if s.userDB == nil { if s.userDB == nil {
return nil, errors.New("user db is not configured") return Config{}, errors.New("user db is not configured")
} }
return queryEnabledBlocks(ctx, s.userDB, appctx.FromContext(ctx)) appCode := appctx.FromContext(ctx)
blocks, err := queryEnabledBlocks(ctx, s.userDB, appCode)
if err != nil {
return Config{}, err
}
whitelist, err := queryEnabledIPWhitelist(ctx, s.userDB, appCode)
if err != nil {
return Config{}, err
}
return Config{Items: blocks, Total: len(blocks), WhitelistItems: whitelist, WhitelistTotal: len(whitelist)}, nil
} }
// Replace 用页面提交的词表整体替换当前 App 的生效配置。 // Replace 用页面提交的词表整体替换当前 App 的生效配置。
// 整体替换比逐条增删更适合配置页保存语义,也能避免客户端和服务端出现半旧半新的屏蔽词集合。 // 整体替换比逐条增删更适合配置页保存语义,也能避免客户端和服务端出现半旧半新的屏蔽词集合。
func (s *Service) Replace(ctx context.Context, actorID int64, keywords []string) ([]RegionBlock, error) { func (s *Service) Replace(ctx context.Context, actorID int64, keywords []string, whitelistIPs *[]string) (Config, error) {
if s.userDB == nil { if s.userDB == nil {
return nil, errors.New("user db is not configured") return Config{}, errors.New("user db is not configured")
} }
normalized, err := normalizeKeywords(ctx, s.userDB, appctx.FromContext(ctx), keywords) normalized, err := normalizeKeywords(ctx, s.userDB, appctx.FromContext(ctx), keywords)
if err != nil { if err != nil {
return nil, err return Config{}, err
}
var normalizedIPs []string
if whitelistIPs != nil {
normalizedIPs, err = normalizeIPWhitelist(*whitelistIPs)
if err != nil {
return Config{}, err
}
} }
tx, err := s.userDB.BeginTx(ctx, nil) tx, err := s.userDB.BeginTx(ctx, nil)
if err != nil { if err != nil {
return nil, err return Config{}, err
} }
defer tx.Rollback() defer tx.Rollback()
@ -80,7 +113,7 @@ func (s *Service) Replace(ctx context.Context, actorID int64, keywords []string)
SET enabled = 0, updated_by_user_id = ?, updated_at_ms = ? SET enabled = 0, updated_by_user_id = ?, updated_at_ms = ?
WHERE app_code = ? AND enabled = 1 WHERE app_code = ? AND enabled = 1
`, nullableActor(actorID), nowMS, appCode); err != nil { `, nullableActor(actorID), nowMS, appCode); err != nil {
return nil, err return Config{}, err
} }
for _, keyword := range normalized { for _, keyword := range normalized {
if _, err := tx.ExecContext(ctx, ` if _, err := tx.ExecContext(ctx, `
@ -95,11 +128,35 @@ func (s *Service) Replace(ctx context.Context, actorID int64, keywords []string)
updated_by_user_id = VALUES(updated_by_user_id), updated_by_user_id = VALUES(updated_by_user_id),
updated_at_ms = VALUES(updated_at_ms) updated_at_ms = VALUES(updated_at_ms)
`, appCode, keyword.Keyword, keyword.CountryCode, nullableActor(actorID), nullableActor(actorID), nowMS, nowMS); err != nil { `, appCode, keyword.Keyword, keyword.CountryCode, nullableActor(actorID), nullableActor(actorID), nowMS, nowMS); err != nil {
return nil, err return Config{}, err
}
}
if whitelistIPs != nil {
if _, err := tx.ExecContext(ctx, `
UPDATE login_risk_ip_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 _, ip := range normalizedIPs {
if _, err := tx.ExecContext(ctx, `
INSERT INTO login_risk_ip_whitelist (
app_code, ip_address, 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, ip, nullableActor(actorID), nullableActor(actorID), nowMS, nowMS); err != nil {
return Config{}, err
}
} }
} }
if err := tx.Commit(); err != nil { if err := tx.Commit(); err != nil {
return nil, err return Config{}, err
} }
return s.List(ctx) return s.List(ctx)
} }
@ -132,6 +189,27 @@ func normalizeKeywords(ctx context.Context, querier countryCodeQuerier, appCode
return result, nil return result, nil
} }
func normalizeIPWhitelist(raw []string) ([]string, error) {
if len(raw) > maxIPWhitelistItems {
return nil, errInvalidPayload
}
result := make([]string, 0, len(raw))
seen := map[string]struct{}{}
for _, item := range raw {
addr, err := netip.ParseAddr(strings.TrimSpace(item))
if err != nil {
return nil, errInvalidPayload
}
ip := addr.String()
if _, ok := seen[ip]; ok {
continue
}
seen[ip] = struct{}{}
result = append(result, ip)
}
return result, nil
}
func resolveCountryCode(ctx context.Context, querier countryCodeQuerier, appCode string, keyword string) (string, error) { func resolveCountryCode(ctx context.Context, querier countryCodeQuerier, appCode string, keyword string) (string, error) {
if countryCodePattern.MatchString(keyword) { if countryCodePattern.MatchString(keyword) {
return strings.ToUpper(keyword), nil return strings.ToUpper(keyword), nil
@ -186,6 +264,32 @@ func queryEnabledBlocks(ctx context.Context, db *sql.DB, appCode string) ([]Regi
return items, nil return items, nil
} }
func queryEnabledIPWhitelist(ctx context.Context, db *sql.DB, appCode string) ([]IPWhitelistItem, error) {
rows, err := db.QueryContext(ctx, `
SELECT whitelist_id, ip_address, enabled, created_at_ms, updated_at_ms
FROM login_risk_ip_whitelist
WHERE app_code = ? AND enabled = 1
ORDER BY ip_address ASC, whitelist_id ASC
`, appCode)
if err != nil {
return nil, err
}
defer rows.Close()
items := make([]IPWhitelistItem, 0)
for rows.Next() {
var item IPWhitelistItem
if err := rows.Scan(&item.WhitelistID, &item.IPAddress, &item.Enabled, &item.CreatedAtMS, &item.UpdatedAtMS); err != nil {
return nil, err
}
items = append(items, item)
}
if err := rows.Err(); err != nil {
return nil, err
}
return items, nil
}
func nullableActor(actorID int64) any { func nullableActor(actorID int64) any {
if actorID <= 0 { if actorID <= 0 {
return nil return nil

View File

@ -17,6 +17,7 @@ type UserAuthClient interface {
RefreshToken(ctx context.Context, req *userv1.RefreshTokenRequest) (*userv1.RefreshTokenResponse, error) RefreshToken(ctx context.Context, req *userv1.RefreshTokenRequest) (*userv1.RefreshTokenResponse, error)
Logout(ctx context.Context, req *userv1.LogoutRequest) (*userv1.LogoutResponse, error) Logout(ctx context.Context, req *userv1.LogoutRequest) (*userv1.LogoutResponse, error)
RecordLoginBlocked(ctx context.Context, req *userv1.RecordLoginBlockedRequest) (*userv1.RecordLoginBlockedResponse, error) RecordLoginBlocked(ctx context.Context, req *userv1.RecordLoginBlockedRequest) (*userv1.RecordLoginBlockedResponse, error)
CheckLoginRiskIPWhitelist(ctx context.Context, req *userv1.CheckLoginRiskIPWhitelistRequest) (*userv1.CheckLoginRiskIPWhitelistResponse, error)
AppHeartbeat(ctx context.Context, req *userv1.AppHeartbeatRequest) (*userv1.AppHeartbeatResponse, error) AppHeartbeat(ctx context.Context, req *userv1.AppHeartbeatRequest) (*userv1.AppHeartbeatResponse, error)
} }
@ -273,6 +274,10 @@ func (c *grpcUserAuthClient) RecordLoginBlocked(ctx context.Context, req *userv1
return c.client.RecordLoginBlocked(ctx, req) return c.client.RecordLoginBlocked(ctx, req)
} }
func (c *grpcUserAuthClient) CheckLoginRiskIPWhitelist(ctx context.Context, req *userv1.CheckLoginRiskIPWhitelistRequest) (*userv1.CheckLoginRiskIPWhitelistResponse, error) {
return c.client.CheckLoginRiskIPWhitelist(ctx, req)
}
func (c *grpcUserAuthClient) AppHeartbeat(ctx context.Context, req *userv1.AppHeartbeatRequest) (*userv1.AppHeartbeatResponse, error) { func (c *grpcUserAuthClient) AppHeartbeat(ctx context.Context, req *userv1.AppHeartbeatRequest) (*userv1.AppHeartbeatResponse, error) {
return c.client.AppHeartbeat(ctx, req) return c.client.AppHeartbeat(ctx, req)
} }

View File

@ -189,6 +189,9 @@ func (h *Handler) allowLoginRisk(writer http.ResponseWriter, request *http.Reque
if !config.Enabled { if !config.Enabled {
return true return true
} }
if h.loginRiskIPWhitelisted(request, input) {
return true
}
if reason := config.FastGuard.blockReason(input.Language, input.Timezone); reason != "" { if reason := config.FastGuard.blockReason(input.Language, input.Timezone); reason != "" {
h.recordLoginBlocked(request, input, reason) h.recordLoginBlocked(request, input, reason)
httpkit.WriteError(writer, request, http.StatusForbidden, httpkit.CodeAuthLoginBlocked, "login blocked") httpkit.WriteError(writer, request, http.StatusForbidden, httpkit.CodeAuthLoginBlocked, "login blocked")
@ -212,6 +215,28 @@ func (h *Handler) allowLoginRisk(writer http.ResponseWriter, request *http.Reque
return true return true
} }
func (h *Handler) loginRiskIPWhitelisted(request *http.Request, input loginRiskInput) bool {
if h.userClient == nil {
return false
}
ip := strings.TrimSpace(clientIP(request))
if ip == "" {
return false
}
ctx, cancel := context.WithTimeout(request.Context(), 500*time.Millisecond)
defer cancel()
resp, err := h.userClient.CheckLoginRiskIPWhitelist(ctx, &userv1.CheckLoginRiskIPWhitelistRequest{
Meta: authRequestMetaWithLoginContext(request, "", input.Platform, input.Language, input.Timezone),
ClientIp: ip,
})
if err != nil {
// 白名单读取失败不能制造额外放行;继续执行原地区屏蔽链路,并保留日志用于排查 Redis/DB 回源异常。
logx.Warn(request.Context(), "login_ip_whitelist_check_failed", slog.String("component", "gateway_login_risk"), slog.String("client_ip_hash", hashLoginRiskIP(ip)), slog.String("error", err.Error()))
return false
}
return resp.GetWhitelisted()
}
func (h *Handler) recordLoginBlocked(request *http.Request, input loginRiskInput, reason string) { func (h *Handler) recordLoginBlocked(request *http.Request, input loginRiskInput, reason string) {
if h.userClient == nil { if h.userClient == nil {
return return

View File

@ -293,12 +293,14 @@ type fakeUserAuthClient struct {
lastRefresh *userv1.RefreshTokenRequest lastRefresh *userv1.RefreshTokenRequest
lastLogout *userv1.LogoutRequest lastLogout *userv1.LogoutRequest
lastBlocked *userv1.RecordLoginBlockedRequest lastBlocked *userv1.RecordLoginBlockedRequest
lastWhitelist *userv1.CheckLoginRiskIPWhitelistRequest
lastAppHeartbeat *userv1.AppHeartbeatRequest lastAppHeartbeat *userv1.AppHeartbeatRequest
loginPasswordCount int loginPasswordCount int
loginThirdCount int loginThirdCount int
refreshCount int refreshCount int
logoutCount int logoutCount int
blockedCount int blockedCount int
whitelisted bool
loginErr error loginErr error
} }
@ -681,6 +683,11 @@ func (f *fakeUserAuthClient) RecordLoginBlocked(_ context.Context, req *userv1.R
return &userv1.RecordLoginBlockedResponse{Recorded: true}, nil return &userv1.RecordLoginBlockedResponse{Recorded: true}, nil
} }
func (f *fakeUserAuthClient) CheckLoginRiskIPWhitelist(_ context.Context, req *userv1.CheckLoginRiskIPWhitelistRequest) (*userv1.CheckLoginRiskIPWhitelistResponse, error) {
f.lastWhitelist = req
return &userv1.CheckLoginRiskIPWhitelistResponse{Whitelisted: f.whitelisted}, nil
}
func (f *fakeUserAuthClient) AppHeartbeat(_ context.Context, req *userv1.AppHeartbeatRequest) (*userv1.AppHeartbeatResponse, error) { func (f *fakeUserAuthClient) AppHeartbeat(_ context.Context, req *userv1.AppHeartbeatRequest) (*userv1.AppHeartbeatResponse, error) {
f.lastAppHeartbeat = req f.lastAppHeartbeat = req
return &userv1.AppHeartbeatResponse{Accepted: true, HeartbeatAtMs: 1_778_000_005_000, ServerTimeMs: 1_778_000_005_000, Token: &userv1.AuthToken{ return &userv1.AppHeartbeatResponse{Accepted: true, HeartbeatAtMs: 1_778_000_005_000, ServerTimeMs: 1_778_000_005_000, Token: &userv1.AuthToken{
@ -5366,6 +5373,33 @@ func TestLoginRiskFastGuardBlocksLanguage(t *testing.T) {
} }
} }
func TestLoginRiskIPWhitelistBypassesFastGuard(t *testing.T) {
userClient := &fakeUserAuthClient{whitelisted: true}
handler := NewHandler(&fakeRoomClient{}, userClient)
handler.SetLoginRisk(LoginRiskConfig{
Enabled: true,
FastGuard: LoginRiskFastGuardConfig{
BlockedLanguagePrimaryTags: []string{"zh"},
},
})
router := handler.Routes(auth.NewVerifier("secret"))
body := []byte(`{"account":"123456","password":"1234567","device_id":"ios","platform":"ios","language":"zh-CN","timezone":"America/Los_Angeles"}`)
request := httptest.NewRequest(http.MethodPost, "/api/v1/auth/account/login", bytes.NewReader(body))
request.Header.Set("X-Request-ID", "req-login-risk-whitelist")
request.Header.Set("X-Forwarded-For", "203.0.113.52")
recorder := httptest.NewRecorder()
router.ServeHTTP(recorder, request)
assertEnvelope(t, recorder, http.StatusOK, httpkit.CodeOK, "req-login-risk-whitelist")
if userClient.loginPasswordCount != 1 || userClient.blockedCount != 0 {
t.Fatalf("whitelisted IP must bypass fast guard: login=%d blocked=%d", userClient.loginPasswordCount, userClient.blockedCount)
}
if userClient.lastWhitelist == nil || userClient.lastWhitelist.GetClientIp() != "203.0.113.52" {
t.Fatalf("whitelist check request mismatch: %+v", userClient.lastWhitelist)
}
}
func TestLoginRiskIPCacheBlocksLogin(t *testing.T) { func TestLoginRiskIPCacheBlocksLogin(t *testing.T) {
userClient := &fakeUserAuthClient{} userClient := &fakeUserAuthClient{}
cache := newMemoryLoginRiskCache() cache := newMemoryLoginRiskCache()

View File

@ -1236,3 +1236,18 @@ CREATE TABLE IF NOT EXISTS login_risk_country_blocks (
KEY idx_login_risk_country_blocks_enabled (app_code, enabled, country_code), KEY idx_login_risk_country_blocks_enabled (app_code, enabled, country_code),
KEY idx_login_risk_country_blocks_updated (app_code, updated_at_ms) KEY idx_login_risk_country_blocks_updated (app_code, updated_at_ms)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='登录风险国家拦截表'; ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='登录风险国家拦截表';
CREATE TABLE IF NOT EXISTS login_risk_ip_whitelist (
app_code VARCHAR(32) NOT NULL COMMENT '应用编码,用于多租户隔离',
whitelist_id BIGINT NOT NULL AUTO_INCREMENT COMMENT '白名单 ID',
ip_address VARCHAR(64) NOT NULL COMMENT '允许跳过登录地区屏蔽的客户端 IP',
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_ip_whitelist_ip (app_code, ip_address),
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 白名单表';

View File

@ -71,3 +71,14 @@ type LoginRiskCountryBlock struct {
CreatedAtMs int64 CreatedAtMs int64
UpdatedAtMs int64 UpdatedAtMs int64
} }
// LoginRiskIPWhitelist 是后台维护的登录地区屏蔽 IP 白名单事实。
// IPAddress 使用 netip 标准化后的明文地址,便于运营审计和登录入口精确匹配。
type LoginRiskIPWhitelist struct {
AppCode string
WhitelistID int64
IPAddress string
Enabled bool
CreatedAtMs int64
UpdatedAtMs int64
}

View File

@ -59,10 +59,37 @@ func (c *RedisIPDecisionCache) SetRevokedSession(ctx context.Context, appCode st
return c.client.Set(ctx, revokedSessionKey(appCode, sessionID), strings.TrimSpace(reason), ttl).Err() return c.client.Set(ctx, revokedSessionKey(appCode, sessionID), strings.TrimSpace(reason), ttl).Err()
} }
func (c *RedisIPDecisionCache) GetIPWhitelist(ctx context.Context, appCode string) ([]string, bool, error) {
raw, err := c.client.Get(ctx, loginRiskIPWhitelistKey(appCode)).Result()
if errors.Is(err, redis.Nil) {
return nil, false, nil
}
if err != nil {
return nil, false, err
}
var ips []string
if err := json.Unmarshal([]byte(raw), &ips); err != nil {
return nil, false, err
}
return ips, true, nil
}
func (c *RedisIPDecisionCache) SetIPWhitelist(ctx context.Context, appCode string, ips []string, ttl time.Duration) error {
raw, err := json.Marshal(ips)
if err != nil {
return err
}
return c.client.Set(ctx, loginRiskIPWhitelistKey(appCode), raw, ttl).Err()
}
func loginRiskIPDecisionKey(appCode string, clientIPHash string) string { func loginRiskIPDecisionKey(appCode string, clientIPHash string) string {
return "login_risk:ip:" + appcode.Normalize(appCode) + ":" + strings.TrimSpace(clientIPHash) return "login_risk:ip:" + appcode.Normalize(appCode) + ":" + strings.TrimSpace(clientIPHash)
} }
func loginRiskIPWhitelistKey(appCode string) string {
return "login_risk:ip_whitelist:" + appcode.Normalize(appCode)
}
func revokedSessionKey(appCode string, sessionID string) string { func revokedSessionKey(appCode string, sessionID string) string {
return "auth:revoked_session:" + appcode.Normalize(appCode) + ":" + strings.TrimSpace(sessionID) return "auth:revoked_session:" + appcode.Normalize(appCode) + ":" + strings.TrimSpace(sessionID)
} }

View File

@ -2,13 +2,17 @@ package auth
import ( import (
"context" "context"
"net/netip"
"sort" "sort"
"strings" "strings"
"time"
"hyapp/pkg/appcode" "hyapp/pkg/appcode"
authdomain "hyapp/services/user-service/internal/domain/auth" authdomain "hyapp/services/user-service/internal/domain/auth"
) )
const loginRiskIPWhitelistCacheTTL = 30 * time.Second
// ListLoginRiskBlockedCountries 返回当前 App 实际生效的登录地区风控词表。 // ListLoginRiskBlockedCountries 返回当前 App 实际生效的登录地区风控词表。
// 静态配置和后台动态配置都下发给 AppApp 可异步做本地风险提示,最终封禁仍以服务端 worker 为准。 // 静态配置和后台动态配置都下发给 AppApp 可异步做本地风险提示,最终封禁仍以服务端 worker 为准。
func (s *Service) ListLoginRiskBlockedCountries(ctx context.Context) ([]authdomain.LoginRiskCountryBlock, error) { func (s *Service) ListLoginRiskBlockedCountries(ctx context.Context) ([]authdomain.LoginRiskCountryBlock, error) {
@ -56,6 +60,54 @@ func (s *Service) ListLoginRiskBlockedCountries(ctx context.Context) ([]authdoma
return blocks, nil return blocks, nil
} }
// CheckLoginRiskIPWhitelisted 判断入口 IP 是否命中后台白名单。
// 白名单是“跳过登录地区屏蔽”的明确运营例外,只做标准化后的单 IP 精确匹配,不扩展为网段或地区推断。
func (s *Service) CheckLoginRiskIPWhitelisted(ctx context.Context, clientIP string) (bool, error) {
ip := normalizeLoginRiskIP(clientIP)
if ip == "" {
return false, nil
}
ips, err := s.listLoginRiskIPWhitelist(ctx)
if err != nil {
return false, err
}
for _, item := range ips {
if item == ip {
return true, nil
}
}
return false, nil
}
func (s *Service) listLoginRiskIPWhitelist(ctx context.Context) ([]string, error) {
appCode := appcode.FromContext(ctx)
if s.ipDecisionCache != nil {
ips, ok, err := s.ipDecisionCache.GetIPWhitelist(ctx, appCode)
if err == nil && ok {
return normalizeLoginRiskIPList(ips), nil
}
}
if s.authRepository == nil {
return nil, nil
}
items, err := s.authRepository.ListEnabledLoginRiskIPWhitelist(ctx)
if err != nil {
return nil, err
}
ips := make([]string, 0, len(items))
for _, item := range items {
if ip := normalizeLoginRiskIP(item.IPAddress); ip != "" {
ips = append(ips, ip)
}
}
ips = normalizeLoginRiskIPList(ips)
if s.ipDecisionCache != nil {
// 白名单读取结果按 App 整体缓存 30 秒;空列表也缓存,避免无配置时每次登录都回源 DB。
_ = s.ipDecisionCache.SetIPWhitelist(ctx, appCode, ips, loginRiskIPWhitelistCacheTTL)
}
return ips, nil
}
func appendUniqueCountryBlock(blocks []authdomain.LoginRiskCountryBlock, seen map[string]struct{}, block authdomain.LoginRiskCountryBlock) []authdomain.LoginRiskCountryBlock { func appendUniqueCountryBlock(blocks []authdomain.LoginRiskCountryBlock, seen map[string]struct{}, block authdomain.LoginRiskCountryBlock) []authdomain.LoginRiskCountryBlock {
key := strings.ToLower(block.CountryCode + "\x00" + block.Keyword) key := strings.ToLower(block.CountryCode + "\x00" + block.Keyword)
if _, ok := seen[key]; ok { if _, ok := seen[key]; ok {
@ -89,3 +141,29 @@ func (s *Service) countryBlockedByDynamicPolicy(ctx context.Context, countryCode
} }
return false, nil return false, nil
} }
func normalizeLoginRiskIP(raw string) string {
addr, err := netip.ParseAddr(strings.TrimSpace(raw))
if err != nil {
return ""
}
return addr.String()
}
func normalizeLoginRiskIPList(raw []string) []string {
seen := make(map[string]struct{}, len(raw))
ips := make([]string, 0, len(raw))
for _, item := range raw {
ip := normalizeLoginRiskIP(item)
if ip == "" {
continue
}
if _, ok := seen[ip]; ok {
continue
}
seen[ip] = struct{}{}
ips = append(ips, ip)
}
sort.Strings(ips)
return ips
}

View File

@ -71,6 +71,19 @@ func normalizeLoginIPRiskWorkerOptions(options LoginIPRiskWorkerOptions) LoginIP
func (s *Service) processLoginIPRiskJob(ctx context.Context, job authdomain.LoginIPRiskJob) error { func (s *Service) processLoginIPRiskJob(ctx context.Context, job authdomain.LoginIPRiskJob) error {
policy := s.loginRiskPolicy policy := s.loginRiskPolicy
nowMs := s.now().UnixMilli() nowMs := s.now().UnixMilli()
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()))
}
if whitelisted {
// 白名单是运营显式放行,命中后不再访问 GeoIP provider也不写 IP blocked 决策,避免后续登录被旧地区缓存误拦。
return s.applyLoginIPRiskDecision(ctx, job, IPDecision{
Decision: authdomain.LoginIPRiskDecisionAllowed,
Source: "ip_whitelist",
CheckedAtMs: nowMs,
ExpiresAtMs: nowMs + loginRiskIPWhitelistCacheTTL.Milliseconds(),
}, nil, authdomain.LoginIPRiskStatusCompleted)
}
if cached, ok := s.freshCachedIPDecision(ctx, job); ok { if cached, ok := s.freshCachedIPDecision(ctx, job); ok {
return s.applyLoginIPRiskDecision(ctx, job, cached, nil, authdomain.LoginIPRiskStatusSkipped) return s.applyLoginIPRiskDecision(ctx, job, cached, nil, authdomain.LoginIPRiskStatusSkipped)
} }

View File

@ -78,6 +78,8 @@ type AuthRepository interface {
CreateLoginIPRiskDecision(ctx context.Context, decision authdomain.LoginIPRiskDecision) error CreateLoginIPRiskDecision(ctx context.Context, decision authdomain.LoginIPRiskDecision) error
// ListEnabledLoginRiskCountryBlocks 返回当前 App 的动态屏蔽国家词表。 // ListEnabledLoginRiskCountryBlocks 返回当前 App 的动态屏蔽国家词表。
ListEnabledLoginRiskCountryBlocks(ctx context.Context) ([]authdomain.LoginRiskCountryBlock, error) ListEnabledLoginRiskCountryBlocks(ctx context.Context) ([]authdomain.LoginRiskCountryBlock, error)
// ListEnabledLoginRiskIPWhitelist 返回当前 App 的登录地区屏蔽 IP 白名单。
ListEnabledLoginRiskIPWhitelist(ctx context.Context) ([]authdomain.LoginRiskIPWhitelist, error)
// MarkLoginAuditBlocked 把登录成功后被异步风控撤销的审计行更新成阻断态。 // MarkLoginAuditBlocked 把登录成功后被异步风控撤销的审计行更新成阻断态。
MarkLoginAuditBlocked(ctx context.Context, requestID string, userID int64, countryCode string, blockReason string) error MarkLoginAuditBlocked(ctx context.Context, requestID string, userID int64, countryCode string, blockReason string) error
} }
@ -165,6 +167,8 @@ type IPDecisionCache interface {
GetIPDecision(ctx context.Context, appCode string, clientIPHash string) (IPDecision, bool, error) GetIPDecision(ctx context.Context, appCode string, clientIPHash string) (IPDecision, bool, error)
SetIPDecision(ctx context.Context, appCode string, clientIPHash string, decision IPDecision, ttl time.Duration) error SetIPDecision(ctx context.Context, appCode string, clientIPHash string, decision IPDecision, ttl time.Duration) error
SetRevokedSession(ctx context.Context, appCode string, sessionID string, reason string, ttl time.Duration) error 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
} }
// IPDecision 是 Redis 中 IP 决策 JSON 的领域投影。 // IPDecision 是 Redis 中 IP 决策 JSON 的领域投影。

View File

@ -23,10 +23,11 @@ type memoryDecisionCache struct {
mu sync.Mutex mu sync.Mutex
ip map[string]authservice.IPDecision ip map[string]authservice.IPDecision
revoked map[string]string revoked map[string]string
ips map[string][]string
} }
func newMemoryDecisionCache() *memoryDecisionCache { func newMemoryDecisionCache() *memoryDecisionCache {
return &memoryDecisionCache{ip: make(map[string]authservice.IPDecision), revoked: make(map[string]string)} return &memoryDecisionCache{ip: make(map[string]authservice.IPDecision), revoked: make(map[string]string), ips: make(map[string][]string)}
} }
func (c *memoryDecisionCache) GetIPDecision(_ context.Context, appCode string, clientIPHash string) (authservice.IPDecision, bool, error) { func (c *memoryDecisionCache) GetIPDecision(_ context.Context, appCode string, clientIPHash string) (authservice.IPDecision, bool, error) {
@ -50,6 +51,20 @@ func (c *memoryDecisionCache) SetRevokedSession(_ context.Context, appCode strin
return nil return nil
} }
func (c *memoryDecisionCache) GetIPWhitelist(_ context.Context, appCode string) ([]string, bool, error) {
c.mu.Lock()
defer c.mu.Unlock()
ips, ok := c.ips[appCode]
return append([]string(nil), ips...), ok, nil
}
func (c *memoryDecisionCache) SetIPWhitelist(_ context.Context, appCode string, ips []string, _ time.Duration) error {
c.mu.Lock()
defer c.mu.Unlock()
c.ips[appCode] = append([]string(nil), ips...)
return nil
}
type sequenceIDGenerator struct { type sequenceIDGenerator struct {
// values 是测试预设 user_id 序列。 // values 是测试预设 user_id 序列。
values []int64 values []int64
@ -552,6 +567,73 @@ func TestLoginIPRiskWorkerRevokesBlockedCountrySession(t *testing.T) {
} }
} }
func TestLoginIPRiskIPWhitelistBypassesBlockedCountry(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_ip_whitelist (
app_code, ip_address, enabled, created_at_ms, updated_at_ms
)
VALUES ('lalu', '203.0.113.80', 1, 1000, 1000)
`); err != nil {
t.Fatalf("seed ip whitelist failed: %v", err)
}
cache := newMemoryDecisionCache()
authSvc := newAuthService(repository, &now, []int64{900024}, []string{"100024"},
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"})),
)
token, _, err := authSvc.LoginThirdParty(ctx, "wechat", "openid-risk-whitelist", thirdPartyRegistration("ios"), authservice.Meta{
AppCode: "lalu",
RequestID: "req-risk-whitelist",
ClientIP: "203.0.113.80",
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-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-whitelist"}); err != nil {
t.Fatalf("whitelisted IP must keep refresh session active: %v", err)
}
if _, ok := cache.ip["lalu:"+authservice.HashLoginRiskIP("203.0.113.80")]; ok {
t.Fatalf("whitelisted IP must not write ordinary IP decision cache")
}
if got := cache.ips["lalu"]; len(got) != 1 || got[0] != "203.0.113.80" {
t.Fatalf("ip whitelist cache mismatch: %+v", got)
}
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-whitelist'
`).Scan(&decision); err != nil {
t.Fatalf("query risk job failed: %v", err)
}
if decision != "allowed" {
t.Fatalf("whitelisted risk job decision mismatch: %s", decision)
}
}
func TestLoginIPRiskWorkerRevokesRotatedSameDeviceSessions(t *testing.T) { func TestLoginIPRiskWorkerRevokesRotatedSameDeviceSessions(t *testing.T) {
// 复现线上竞态:登录后风控任务尚未完成,客户端连续 refresh 轮换 session。 // 复现线上竞态:登录后风控任务尚未完成,客户端连续 refresh 轮换 session。
// blocked 决策落地时必须沿源 session 的同设备登录链路清掉后续 session否则最新 access/refresh 仍能继续使用。 // blocked 决策落地时必须沿源 session 的同设备登录链路清掉后续 session否则最新 access/refresh 仍能继续使用。

View File

@ -131,6 +131,36 @@ func (r *Repository) ListEnabledLoginRiskCountryBlocks(ctx context.Context) ([]a
return blocks, nil return blocks, nil
} }
func (r *Repository) ListEnabledLoginRiskIPWhitelist(ctx context.Context) ([]authdomain.LoginRiskIPWhitelist, error) {
rows, err := r.db.QueryContext(ctx, `
SELECT app_code, whitelist_id, ip_address, enabled, created_at_ms, updated_at_ms
FROM login_risk_ip_whitelist
WHERE app_code = ? AND enabled = 1
ORDER BY ip_address ASC, whitelist_id ASC
`, appcode.FromContext(ctx))
if err != nil {
return nil, err
}
defer rows.Close()
items := make([]authdomain.LoginRiskIPWhitelist, 0)
for rows.Next() {
var item authdomain.LoginRiskIPWhitelist
if err := rows.Scan(&item.AppCode, &item.WhitelistID, &item.IPAddress, &item.Enabled, &item.CreatedAtMs, &item.UpdatedAtMs); err != nil {
return nil, err
}
item.IPAddress = strings.TrimSpace(item.IPAddress)
if item.IPAddress == "" {
continue
}
items = append(items, item)
}
if err := rows.Err(); err != nil {
return nil, err
}
return items, nil
}
type loginIPRiskJobScanner interface { type loginIPRiskJobScanner interface {
Scan(dest ...any) error Scan(dest ...any) error
} }

View File

@ -394,6 +394,11 @@ func (r *Repository) ListEnabledLoginRiskCountryBlocks(ctx context.Context) ([]a
return r.Repository.AuthRepository().ListEnabledLoginRiskCountryBlocks(ctx) return r.Repository.AuthRepository().ListEnabledLoginRiskCountryBlocks(ctx)
} }
// ListEnabledLoginRiskIPWhitelist 让测试 wrapper 继续满足认证风控接口。
func (r *Repository) ListEnabledLoginRiskIPWhitelist(ctx context.Context) ([]authdomain.LoginRiskIPWhitelist, error) {
return r.Repository.AuthRepository().ListEnabledLoginRiskIPWhitelist(ctx)
}
// MarkLoginAuditBlocked 让测试 wrapper 继续满足认证接口。 // MarkLoginAuditBlocked 让测试 wrapper 继续满足认证接口。
func (r *Repository) MarkLoginAuditBlocked(ctx context.Context, requestID string, userID int64, countryCode string, blockReason string) error { 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) return r.Repository.AuthRepository().MarkLoginAuditBlocked(ctx, requestID, userID, countryCode, blockReason)

View File

@ -212,6 +212,16 @@ func (s *Server) RecordLoginBlocked(ctx context.Context, req *userv1.RecordLogin
return &userv1.RecordLoginBlockedResponse{Recorded: recorded}, nil return &userv1.RecordLoginBlockedResponse{Recorded: recorded}, nil
} }
// CheckLoginRiskIPWhitelist 判断登录入口 IP 是否命中后台白名单gateway 用它在快拦截前保留运营例外。
func (s *Server) CheckLoginRiskIPWhitelist(ctx context.Context, req *userv1.CheckLoginRiskIPWhitelistRequest) (*userv1.CheckLoginRiskIPWhitelistResponse, error) {
ctx = contextWithApp(ctx, req.GetMeta())
whitelisted, err := s.authSvc.CheckLoginRiskIPWhitelisted(ctx, req.GetClientIp())
if err != nil {
return nil, xerr.ToGRPCError(err)
}
return &userv1.CheckLoginRiskIPWhitelistResponse{Whitelisted: whitelisted}, nil
}
// AppHeartbeat 刷新当前 App 登录会话最近在线时间,不创建房间或麦位 presence。 // AppHeartbeat 刷新当前 App 登录会话最近在线时间,不创建房间或麦位 presence。
func (s *Server) AppHeartbeat(ctx context.Context, req *userv1.AppHeartbeatRequest) (*userv1.AppHeartbeatResponse, error) { func (s *Server) AppHeartbeat(ctx context.Context, req *userv1.AppHeartbeatRequest) (*userv1.AppHeartbeatResponse, error) {
ctx = contextWithApp(ctx, req.GetMeta()) ctx = contextWithApp(ctx, req.GetMeta())