Files
scrabble-game/platform/telegram/internal/loginwidget/validator_test.go
T
developer 01485d8fc6
Tests · Go / test (push) Successful in 7s
Tests · Integration / integration (push) Successful in 11s
Tests · UI / test (push) Successful in 18s
Stage 11: account linking & merge (email + Telegram Login Widget) (#12)
2026-06-04 09:18:17 +00:00

99 lines
2.9 KiB
Go

package loginwidget
import (
"crypto/hmac"
"crypto/sha256"
"encoding/hex"
"errors"
"net/url"
"sort"
"strconv"
"strings"
"testing"
"time"
)
const testToken = "123456:TESTTOKEN"
// signWidget builds validly signed Login Widget data for the token and fields,
// mirroring Telegram's algorithm (secret = SHA-256(token); HMAC over the sorted
// data-check string).
func signWidget(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 := sha256.Sum256([]byte(token))
mac := hmac.New(sha256.New, secret[:])
mac.Write([]byte(strings.Join(lines, "\n")))
v := url.Values{}
for k, val := range fields {
v.Set(k, val)
}
v.Set("hash", hex.EncodeToString(mac.Sum(nil)))
return v.Encode()
}
func freshFields() map[string]string {
return map[string]string{
"auth_date": strconv.FormatInt(time.Now().Unix(), 10),
"id": "42",
"username": "neo",
"first_name": "Thomas",
}
}
func TestValidateOK(t *testing.T) {
data := signWidget(testToken, freshFields())
u, err := NewHMACValidator(testToken).Validate(data)
if err != nil {
t.Fatalf("validate: %v", err)
}
if u.ExternalID != "42" || u.Username != "neo" || u.FirstName != "Thomas" {
t.Errorf("user = %+v, want {42 neo Thomas}", u)
}
}
func TestValidateRejects(t *testing.T) {
valid := signWidget(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, ErrInvalidLoginWidget) {
t.Errorf("err = %v, want ErrInvalidLoginWidget", err)
}
})
t.Run("wrong token", func(t *testing.T) {
if _, err := NewHMACValidator("other:TOKEN").Validate(valid); !errors.Is(err, ErrInvalidLoginWidget) {
t.Errorf("err = %v, want ErrInvalidLoginWidget", err)
}
})
t.Run("tampered field", func(t *testing.T) {
// Flip the id after signing: the HMAC must no longer match.
forged := strings.Replace(valid, "id=42", "id=43", 1)
if _, err := NewHMACValidator(testToken).Validate(forged); !errors.Is(err, ErrInvalidLoginWidget) {
t.Errorf("err = %v, want ErrInvalidLoginWidget", err)
}
})
t.Run("missing hash", func(t *testing.T) {
if _, err := NewHMACValidator(testToken).Validate("id=42&auth_date=1"); !errors.Is(err, ErrInvalidLoginWidget) {
t.Errorf("err = %v, want ErrInvalidLoginWidget", err)
}
})
t.Run("stale auth_date", func(t *testing.T) {
stale := signWidget(testToken, map[string]string{
"auth_date": strconv.FormatInt(time.Now().Add(-48*time.Hour).Unix(), 10),
"id": "42",
})
if _, err := NewHMACValidator(testToken).Validate(stale); !errors.Is(err, ErrInvalidLoginWidget) {
t.Errorf("err = %v, want ErrInvalidLoginWidget", err)
}
})
}