用户靓号白名单
This commit is contained in:
parent
9717929231
commit
82dd3cc300
@ -13,6 +13,9 @@ import (
|
||||
|
||||
"hyapp-admin-server/internal/appctx"
|
||||
"hyapp-admin-server/internal/integration/userclient"
|
||||
|
||||
"google.golang.org/grpc/codes"
|
||||
"google.golang.org/grpc/status"
|
||||
)
|
||||
|
||||
const (
|
||||
@ -20,6 +23,7 @@ const (
|
||||
maxRegionBlockKeywordLength = 128
|
||||
maxIPWhitelistItems = 500
|
||||
maxUserWhitelistItems = 500
|
||||
maxUserWhitelistLookupLength = 64
|
||||
)
|
||||
|
||||
var (
|
||||
@ -51,11 +55,12 @@ type IPWhitelistItem struct {
|
||||
}
|
||||
|
||||
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"`
|
||||
WhitelistID int64 `json:"whitelist_id"`
|
||||
UserID int64 `json:"user_id,string"`
|
||||
DisplayUserID string `json:"display_user_id"`
|
||||
Enabled bool `json:"enabled"`
|
||||
CreatedAtMS int64 `json:"created_at_ms"`
|
||||
UpdatedAtMS int64 `json:"updated_at_ms"`
|
||||
}
|
||||
|
||||
type Config struct {
|
||||
@ -121,7 +126,7 @@ func (s *Service) Replace(ctx context.Context, requestID string, actorID int64,
|
||||
}
|
||||
var normalizedUserIDs []int64
|
||||
if whitelistUserIDs != nil {
|
||||
normalizedUserIDs, err = normalizeUserIDWhitelist(*whitelistUserIDs)
|
||||
normalizedUserIDs, err = s.normalizeUserIDWhitelist(ctx, requestID, *whitelistUserIDs)
|
||||
if err != nil {
|
||||
return Config{}, err
|
||||
}
|
||||
@ -265,7 +270,7 @@ func normalizeIPWhitelist(raw []string) ([]string, error) {
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func normalizeUserIDWhitelist(raw []string) ([]int64, error) {
|
||||
func (s *Service) normalizeUserIDWhitelist(ctx context.Context, requestID string, raw []string) ([]int64, error) {
|
||||
if len(raw) > maxUserWhitelistItems {
|
||||
return nil, errInvalidPayload
|
||||
}
|
||||
@ -276,7 +281,7 @@ func normalizeUserIDWhitelist(raw []string) ([]int64, error) {
|
||||
if value == "" {
|
||||
return nil, errInvalidPayload
|
||||
}
|
||||
userID, err := strconv.ParseInt(value, 10, 64)
|
||||
userID, err := s.resolveWhitelistUserID(ctx, requestID, value)
|
||||
if err != nil || userID <= 0 {
|
||||
return nil, errInvalidPayload
|
||||
}
|
||||
@ -289,6 +294,75 @@ func normalizeUserIDWhitelist(raw []string) ([]int64, error) {
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (s *Service) resolveWhitelistUserID(ctx context.Context, requestID string, raw string) (int64, error) {
|
||||
value := strings.TrimSpace(raw)
|
||||
if value == "" || utf8.RuneCountInString(value) > maxUserWhitelistLookupLength {
|
||||
return 0, errInvalidPayload
|
||||
}
|
||||
if s.user == nil {
|
||||
// 本地隔离测试没有 user-service client 时只接受真实 user_id,避免把短号误写成未知用户。
|
||||
numericID, numericOK := parsePositiveInt64(value)
|
||||
if !numericOK {
|
||||
return 0, errInvalidPayload
|
||||
}
|
||||
return numericID, nil
|
||||
}
|
||||
|
||||
// 后台输入优先按当前 active 展示号解析,默认短号和靓号都由 user-service 统一处理;
|
||||
// 保存时只落真实 user_id,后续用户改短号或靓号过期也不会影响登录风控白名单。
|
||||
if identity, err := s.user.ResolveDisplayUserID(ctx, userclient.ResolveDisplayUserIDRequest{
|
||||
RequestID: requestID,
|
||||
Caller: "admin-server",
|
||||
DisplayUserID: value,
|
||||
}); err != nil {
|
||||
if !isGRPCNotFound(err) && !isGRPCInvalidArgument(err) {
|
||||
return 0, err
|
||||
}
|
||||
} else if identity != nil && identity.UserID > 0 {
|
||||
return identity.UserID, nil
|
||||
}
|
||||
|
||||
// 展示号没有命中时,纯数字输入再兜底按内部 user_id 校验;非数字靓号未命中就直接视为无效配置。
|
||||
numericID, numericOK := parsePositiveInt64(value)
|
||||
if !numericOK {
|
||||
return 0, errInvalidPayload
|
||||
}
|
||||
if found, err := s.userExists(ctx, requestID, numericID); err != nil {
|
||||
return 0, err
|
||||
} else if found {
|
||||
return numericID, nil
|
||||
}
|
||||
return 0, errInvalidPayload
|
||||
}
|
||||
|
||||
func (s *Service) userExists(ctx context.Context, requestID string, userID int64) (bool, error) {
|
||||
user, err := s.user.GetUser(ctx, userclient.GetUserRequest{
|
||||
RequestID: requestID,
|
||||
Caller: "admin-server",
|
||||
UserID: userID,
|
||||
})
|
||||
if err != nil {
|
||||
if isGRPCNotFound(err) {
|
||||
return false, nil
|
||||
}
|
||||
return false, err
|
||||
}
|
||||
return user != nil && user.UserID > 0, nil
|
||||
}
|
||||
|
||||
func parsePositiveInt64(text string) (int64, bool) {
|
||||
parsed, err := strconv.ParseInt(strings.TrimSpace(text), 10, 64)
|
||||
return parsed, err == nil && parsed > 0
|
||||
}
|
||||
|
||||
func isGRPCNotFound(err error) bool {
|
||||
return status.Code(err) == codes.NotFound
|
||||
}
|
||||
|
||||
func isGRPCInvalidArgument(err error) bool {
|
||||
return status.Code(err) == codes.InvalidArgument
|
||||
}
|
||||
|
||||
func (s *Service) refreshWhitelistCache(ctx context.Context, requestID string) error {
|
||||
if s.user == nil {
|
||||
return nil
|
||||
@ -385,10 +459,13 @@ func queryEnabledIPWhitelist(ctx context.Context, db *sql.DB, appCode string) ([
|
||||
|
||||
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
|
||||
SELECT w.whitelist_id, w.user_id, COALESCE(u.current_display_user_id, ''), w.enabled, w.created_at_ms, w.updated_at_ms
|
||||
FROM login_risk_user_whitelist w
|
||||
LEFT JOIN users u
|
||||
ON u.app_code = w.app_code
|
||||
AND u.user_id = w.user_id
|
||||
WHERE w.app_code = ? AND w.enabled = 1
|
||||
ORDER BY w.user_id ASC, w.whitelist_id ASC
|
||||
`, appCode)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@ -398,7 +475,7 @@ func queryEnabledUserWhitelist(ctx context.Context, db *sql.DB, appCode string)
|
||||
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 {
|
||||
if err := rows.Scan(&item.WhitelistID, &item.UserID, &item.DisplayUserID, &item.Enabled, &item.CreatedAtMS, &item.UpdatedAtMS); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if item.UserID > 0 {
|
||||
|
||||
@ -7,13 +7,18 @@ import (
|
||||
"testing"
|
||||
|
||||
"hyapp-admin-server/internal/integration/userclient"
|
||||
|
||||
"google.golang.org/grpc/codes"
|
||||
"google.golang.org/grpc/status"
|
||||
)
|
||||
|
||||
type fakeWhitelistCacheUserClient struct {
|
||||
userclient.Client
|
||||
|
||||
called bool
|
||||
err error
|
||||
called bool
|
||||
err error
|
||||
users map[int64]*userclient.User
|
||||
identities map[string]*userclient.UserIdentity
|
||||
}
|
||||
|
||||
func (f *fakeWhitelistCacheUserClient) RefreshLoginRiskWhitelistCache(_ context.Context, req userclient.RefreshLoginRiskWhitelistCacheRequest) (*userclient.LoginRiskWhitelistCacheSnapshot, error) {
|
||||
@ -27,20 +32,50 @@ func (f *fakeWhitelistCacheUserClient) RefreshLoginRiskWhitelistCache(_ context.
|
||||
return &userclient.LoginRiskWhitelistCacheSnapshot{Refreshed: true, UserWhitelistCount: 2}, nil
|
||||
}
|
||||
|
||||
func TestNormalizeUserIDWhitelistDeduplicatesPositiveIDs(t *testing.T) {
|
||||
got, err := normalizeUserIDWhitelist([]string{" 900025 ", "900026", "900025"})
|
||||
func (f *fakeWhitelistCacheUserClient) ResolveDisplayUserID(_ context.Context, req userclient.ResolveDisplayUserIDRequest) (*userclient.UserIdentity, error) {
|
||||
if req.RequestID != "req-refresh" || req.Caller != "admin-server" {
|
||||
return nil, errors.New("unexpected resolve request")
|
||||
}
|
||||
if identity := f.identities[req.DisplayUserID]; identity != nil {
|
||||
return identity, nil
|
||||
}
|
||||
return nil, status.Error(codes.NotFound, "display user id not found")
|
||||
}
|
||||
|
||||
func (f *fakeWhitelistCacheUserClient) GetUser(_ context.Context, req userclient.GetUserRequest) (*userclient.User, error) {
|
||||
if req.RequestID != "req-refresh" || req.Caller != "admin-server" {
|
||||
return nil, errors.New("unexpected get user request")
|
||||
}
|
||||
if user := f.users[req.UserID]; user != nil {
|
||||
return user, nil
|
||||
}
|
||||
return nil, status.Error(codes.NotFound, "user not found")
|
||||
}
|
||||
|
||||
func TestNormalizeUserIDWhitelistResolvesDisplayIDsAndInternalIDs(t *testing.T) {
|
||||
service := NewService(nil, &fakeWhitelistCacheUserClient{
|
||||
users: map[int64]*userclient.User{
|
||||
990000: {UserID: 990000},
|
||||
},
|
||||
identities: map[string]*userclient.UserIdentity{
|
||||
"VIP2026": {UserID: 900025, DisplayUserID: "VIP2026"},
|
||||
"123456789012": {UserID: 900026, DisplayUserID: "123456789012"},
|
||||
},
|
||||
})
|
||||
got, err := service.normalizeUserIDWhitelist(context.Background(), "req-refresh", []string{" VIP2026 ", "123456789012", "990000", "VIP2026"})
|
||||
if err != nil {
|
||||
t.Fatalf("normalize user whitelist failed: %v", err)
|
||||
}
|
||||
want := []int64{900025, 900026}
|
||||
want := []int64{900025, 900026, 990000}
|
||||
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) {
|
||||
service := NewService(nil, &fakeWhitelistCacheUserClient{})
|
||||
for _, raw := range [][]string{{""}, {"0"}, {"-1"}, {"VIP-2026"}, {"123456"}} {
|
||||
if _, err := service.normalizeUserIDWhitelist(context.Background(), "req-refresh", raw); !errors.Is(err, errInvalidPayload) {
|
||||
t.Fatalf("normalize %v error = %v, want errInvalidPayload", raw, err)
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user