feat(telegram): deny bot users at initData validation
CI / changes (pull_request) Successful in 2s
CI / unit (pull_request) Successful in 9s
CI / integration (pull_request) Successful in 15s
CI / ui (pull_request) Successful in 55s
CI / gate (pull_request) Successful in 0s
CI / deploy (pull_request) Successful in 1m19s
CI / changes (pull_request) Successful in 2s
CI / unit (pull_request) Successful in 9s
CI / integration (pull_request) Successful in 15s
CI / ui (pull_request) Successful in 55s
CI / gate (pull_request) Successful in 0s
CI / deploy (pull_request) Successful in 1m19s
The validator parsed only id/username/first_name/language_code from the signed Telegram user, so a WebAppUser flagged is_bot would have been provisioned a normal account. The HMAC already proves Telegram signed the payload, so is_bot==true is Telegram itself attesting the launching principal is a bot. Parse is_bot and reject it (ErrInvalidInitData -> gateway 4xx -> launch error). A real user opening the Mini App never carries it, so this is a defensive deny. Tests cover both the denied (is_bot true) and allowed (is_bot false) paths.
This commit is contained in:
@@ -18,7 +18,8 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
// ErrInvalidInitData is returned when initData fails HMAC validation, is missing
|
// 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")
|
var ErrInvalidInitData = errors.New("initdata: invalid telegram init data")
|
||||||
|
|
||||||
// defaultMaxAge bounds how old a validated initData payload may be.
|
// defaultMaxAge bounds how old a validated initData payload may be.
|
||||||
@@ -120,6 +121,7 @@ func parseUser(userJSON string) (User, error) {
|
|||||||
}
|
}
|
||||||
var u struct {
|
var u struct {
|
||||||
ID int64 `json:"id"`
|
ID int64 `json:"id"`
|
||||||
|
IsBot bool `json:"is_bot"`
|
||||||
Username string `json:"username"`
|
Username string `json:"username"`
|
||||||
FirstName string `json:"first_name"`
|
FirstName string `json:"first_name"`
|
||||||
LanguageCode string `json:"language_code"`
|
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 {
|
if err := json.Unmarshal([]byte(userJSON), &u); err != nil || u.ID == 0 {
|
||||||
return User{}, ErrInvalidInitData
|
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{
|
return User{
|
||||||
ExternalID: strconv.FormatInt(u.ID, 10),
|
ExternalID: strconv.FormatInt(u.ID, 10),
|
||||||
Username: u.Username,
|
Username: u.Username,
|
||||||
|
|||||||
@@ -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)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user