diff --git a/platform/telegram/internal/initdata/validator.go b/platform/telegram/internal/initdata/validator.go index d09c8e3..a4d35ba 100644 --- a/platform/telegram/internal/initdata/validator.go +++ b/platform/telegram/internal/initdata/validator.go @@ -18,7 +18,8 @@ import ( ) // ErrInvalidInitData is returned when initData fails HMAC validation, is missing -// the hash, is malformed, or is older than the freshness window. +// the hash, is malformed, is older than the freshness window, or identifies a bot +// user (is_bot), which is denied. var ErrInvalidInitData = errors.New("initdata: invalid telegram init data") // defaultMaxAge bounds how old a validated initData payload may be. @@ -120,6 +121,7 @@ func parseUser(userJSON string) (User, error) { } var u struct { ID int64 `json:"id"` + IsBot bool `json:"is_bot"` Username string `json:"username"` FirstName string `json:"first_name"` LanguageCode string `json:"language_code"` @@ -127,6 +129,12 @@ func parseUser(userJSON string) (User, error) { if err := json.Unmarshal([]byte(userJSON), &u); err != nil || u.ID == 0 { return User{}, ErrInvalidInitData } + // Deny bot principals: the HMAC has already proved Telegram signed this payload, so is_bot==true + // is Telegram itself attesting the launching user is a bot. A real user opening the Mini App + // never carries it, so reject defensively rather than provision an account for a bot. + if u.IsBot { + return User{}, ErrInvalidInitData + } return User{ ExternalID: strconv.FormatInt(u.ID, 10), Username: u.Username, diff --git a/platform/telegram/internal/initdata/validator_test.go b/platform/telegram/internal/initdata/validator_test.go index 9f525cd..82b4065 100644 --- a/platform/telegram/internal/initdata/validator_test.go +++ b/platform/telegram/internal/initdata/validator_test.go @@ -83,3 +83,28 @@ func TestValidateRejects(t *testing.T) { } }) } + +func TestValidateBotUser(t *testing.T) { + t.Run("is_bot true is denied", func(t *testing.T) { + initData := signInitData(testToken, map[string]string{ + "auth_date": strconv.FormatInt(time.Now().Unix(), 10), + "user": `{"id":42,"is_bot":true,"first_name":"Robo"}`, + }) + if _, err := NewHMACValidator(testToken).Validate(initData); !errors.Is(err, ErrInvalidInitData) { + t.Errorf("err = %v, want ErrInvalidInitData", err) + } + }) + t.Run("is_bot false is allowed", func(t *testing.T) { + initData := signInitData(testToken, map[string]string{ + "auth_date": strconv.FormatInt(time.Now().Unix(), 10), + "user": `{"id":42,"is_bot":false,"first_name":"Thomas"}`, + }) + u, err := NewHMACValidator(testToken).Validate(initData) + if err != nil { + t.Fatalf("validate: %v", err) + } + if u.ExternalID != "42" || u.FirstName != "Thomas" { + t.Errorf("user = %+v, want {42 Thomas}", u) + } + }) +}