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) } }) }