From 9f79226b808b7decbbf1291109347ed5a48e70bc Mon Sep 17 00:00:00 2001 From: Vadym Date: Sun, 2 Jun 2024 16:29:38 +0300 Subject: [PATCH] feat: added user first/last/nick name validation (#42) * feat: added user first/last/nick name validation * fix: added top level t.Parallel() call --------- Co-authored-by: hrvadl --- internal/handler/join/handler.go | 39 ++++++++- internal/handler/join/validator/lists.go | 7 ++ internal/handler/join/validator/name.go | 29 +++++++ internal/handler/join/validator/name_test.go | 84 ++++++++++++++++++++ 4 files changed, 157 insertions(+), 2 deletions(-) create mode 100644 internal/handler/join/validator/lists.go create mode 100644 internal/handler/join/validator/name.go create mode 100644 internal/handler/join/validator/name_test.go diff --git a/internal/handler/join/handler.go b/internal/handler/join/handler.go index 60db4d6..def5117 100644 --- a/internal/handler/join/handler.go +++ b/internal/handler/join/handler.go @@ -2,13 +2,16 @@ package join import ( "context" + "fmt" "log/slog" "github.com/mymmrac/telego" th "github.com/mymmrac/telego/telegohandler" tu "github.com/mymmrac/telego/telegoutil" + "github.com/spf13/viper" "github.com/GolangUA/telegram-butler/internal/handler/callback/callbackdata" + "github.com/GolangUA/telegram-butler/internal/handler/join/validator" "github.com/GolangUA/telegram-butler/internal/messages" "github.com/GolangUA/telegram-butler/internal/module/logger" ) @@ -19,11 +22,19 @@ const ( ) func Register(bh *th.BotHandler) { - h := &handler{} + h := &handler{ + validator: validator.New(validator.DefaultForbiddenList), + } bh.HandleChatJoinRequestCtx(h.chatJoinRequest) } -type handler struct{} +type nameValidator interface { + Validate(names ...string) bool +} + +type handler struct { + validator nameValidator +} func (h *handler) chatJoinRequest(ctx context.Context, bot *telego.Bot, request telego.ChatJoinRequest) { log := logger.FromContext(ctx) @@ -35,6 +46,30 @@ func (h *handler) chatJoinRequest(ctx context.Context, bot *telego.Bot, request log.Info("[JOIN REQUEST]") + if !h.validator.Validate(request.From.FirstName, request.From.LastName, request.From.Username) { + log.Info("Name validation is failed") + err := bot.DeclineChatJoinRequest(&telego.DeclineChatJoinRequestParams{ + UserID: request.From.ID, + ChatID: tu.ID(request.Chat.ID), + }) + if err != nil { + log.Error("Decline join request failed", slog.Any("error", err)) + return + } + + msg := fmt.Sprintf(messages.Decline, viper.GetString("admin-username")) + _, err = bot.SendMessage(&telego.SendMessageParams{ + ChatID: tu.ID(request.From.ID), + Text: msg, + ReplyMarkup: tu.ReplyKeyboardRemove(), + }) + if err != nil { + log.Error("Sending decision message failed", slog.Any("error", err)) + } + + return + } + k := tu.InlineKeyboard( tu.InlineKeyboardRow( telego.InlineKeyboardButton{ diff --git a/internal/handler/join/validator/lists.go b/internal/handler/join/validator/lists.go new file mode 100644 index 0000000..d7c462b --- /dev/null +++ b/internal/handler/join/validator/lists.go @@ -0,0 +1,7 @@ +package validator + +var DefaultForbiddenList = []string{ + "🇷🇺", + "🪆", + "Russia", +} diff --git a/internal/handler/join/validator/name.go b/internal/handler/join/validator/name.go new file mode 100644 index 0000000..5043ef6 --- /dev/null +++ b/internal/handler/join/validator/name.go @@ -0,0 +1,29 @@ +package validator + +import "strings" + +func New(forbiddenWords []string) *ByName { + for i, w := range forbiddenWords { + forbiddenWords[i] = strings.ToLower(w) + } + + return &ByName{ + forbiddenWords: forbiddenWords, + } +} + +type ByName struct { + forbiddenWords []string +} + +func (v *ByName) Validate(names ...string) bool { + for _, name := range names { + lowerName := strings.ToLower(name) + for _, word := range v.forbiddenWords { + if strings.Contains(lowerName, word) { + return false + } + } + } + return true +} diff --git a/internal/handler/join/validator/name_test.go b/internal/handler/join/validator/name_test.go new file mode 100644 index 0000000..420eebd --- /dev/null +++ b/internal/handler/join/validator/name_test.go @@ -0,0 +1,84 @@ +package validator + +import "testing" + +func TestValidateByName(t *testing.T) { + t.Parallel() + tc := []struct { + name string + forbiddenWords []string + words []string + expected bool + }{ + { + name: "Should forbid russian flag emoji", + forbiddenWords: []string{"🇷🇺"}, + words: []string{"🇷🇺"}, + expected: false, + }, + { + name: "Should forbid russian matryoshka emoji", + forbiddenWords: []string{"🪆"}, + words: []string{"🪆"}, + expected: false, + }, + { + name: "Should forbid word 'Russia'", + forbiddenWords: []string{"Russia"}, + words: []string{"Russia"}, + expected: false, + }, + { + name: "Should forbid word 'russia'", + forbiddenWords: []string{"russia"}, + words: []string{"russia"}, + expected: false, + }, + { + name: "Should allow 'Vadym' name to bypass", + forbiddenWords: []string{"russia"}, + words: []string{"Vadym"}, + expected: true, + }, + { + name: "Should allow 'Ruslan' name to bypass", + forbiddenWords: []string{"russia"}, + words: []string{"Ruslan"}, + expected: true, + }, + { + name: "Should allow anything when forbidden words are empty", + forbiddenWords: []string{}, + words: []string{"russia", "Russia"}, + expected: true, + }, + { + name: "Should allow anything when nil slice of forbidden words are passed", + forbiddenWords: nil, + words: []string{"russia", "Russia"}, + expected: true, + }, + { + name: "Should allow when empty slice of words are passed", + forbiddenWords: []string{"russia"}, + words: []string{}, + expected: true, + }, + { + name: "Should allow when nil slice of words are passed", + forbiddenWords: []string{"russia"}, + words: nil, + expected: true, + }, + } + + for _, tt := range tc { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + v := New(tt.forbiddenWords) + if got := v.Validate(tt.words...); got != tt.expected { + t.Errorf("Expected to get: %v, but got: %v", tt.expected, got) + } + }) + } +}