8de9fb1ecd
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.
111 lines
3.4 KiB
Go
111 lines
3.4 KiB
Go
package initdata
|
|
|
|
import (
|
|
"encoding/hex"
|
|
"errors"
|
|
"net/url"
|
|
"sort"
|
|
"strconv"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
const testToken = "123456:TESTTOKEN"
|
|
|
|
// signInitData builds a validly signed initData query string for the given token
|
|
// and decoded fields, mirroring Telegram's data-check algorithm.
|
|
func signInitData(token string, fields map[string]string) string {
|
|
keys := make([]string, 0, len(fields))
|
|
for k := range fields {
|
|
keys = append(keys, k)
|
|
}
|
|
sort.Strings(keys)
|
|
lines := make([]string, 0, len(keys))
|
|
for _, k := range keys {
|
|
lines = append(lines, k+"="+fields[k])
|
|
}
|
|
secret := hmacSHA256([]byte("WebAppData"), []byte(token))
|
|
mac := hmacSHA256(secret, []byte(strings.Join(lines, "\n")))
|
|
|
|
v := url.Values{}
|
|
for k, val := range fields {
|
|
v.Set(k, val)
|
|
}
|
|
v.Set("hash", hex.EncodeToString(mac))
|
|
return v.Encode()
|
|
}
|
|
|
|
func freshFields() map[string]string {
|
|
return map[string]string{
|
|
"auth_date": strconv.FormatInt(time.Now().Unix(), 10),
|
|
"user": `{"id":42,"username":"neo","first_name":"Thomas","language_code":"ru"}`,
|
|
}
|
|
}
|
|
|
|
func TestValidateOK(t *testing.T) {
|
|
initData := signInitData(testToken, freshFields())
|
|
u, err := NewHMACValidator(testToken).Validate(initData)
|
|
if err != nil {
|
|
t.Fatalf("validate: %v", err)
|
|
}
|
|
if u.ExternalID != "42" || u.Username != "neo" || u.FirstName != "Thomas" || u.LanguageCode != "ru" {
|
|
t.Errorf("user = %+v, want {42 neo Thomas ru}", u)
|
|
}
|
|
}
|
|
|
|
func TestValidateRejects(t *testing.T) {
|
|
valid := signInitData(testToken, freshFields())
|
|
|
|
t.Run("tampered hash", func(t *testing.T) {
|
|
tampered := strings.Replace(valid, "hash=", "hash=00", 1)
|
|
if _, err := NewHMACValidator(testToken).Validate(tampered); !errors.Is(err, ErrInvalidInitData) {
|
|
t.Errorf("err = %v, want ErrInvalidInitData", err)
|
|
}
|
|
})
|
|
t.Run("wrong token", func(t *testing.T) {
|
|
if _, err := NewHMACValidator("other:TOKEN").Validate(valid); !errors.Is(err, ErrInvalidInitData) {
|
|
t.Errorf("err = %v, want ErrInvalidInitData", err)
|
|
}
|
|
})
|
|
t.Run("missing hash", func(t *testing.T) {
|
|
if _, err := NewHMACValidator(testToken).Validate("user=%7B%7D&auth_date=1"); !errors.Is(err, ErrInvalidInitData) {
|
|
t.Errorf("err = %v, want ErrInvalidInitData", err)
|
|
}
|
|
})
|
|
t.Run("stale auth_date", func(t *testing.T) {
|
|
stale := signInitData(testToken, map[string]string{
|
|
"auth_date": strconv.FormatInt(time.Now().Add(-48*time.Hour).Unix(), 10),
|
|
"user": `{"id":42}`,
|
|
})
|
|
if _, err := NewHMACValidator(testToken).Validate(stale); !errors.Is(err, ErrInvalidInitData) {
|
|
t.Errorf("err = %v, want ErrInvalidInitData", err)
|
|
}
|
|
})
|
|
}
|
|
|
|
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)
|
|
}
|
|
})
|
|
}
|