diff --git a/validity.go b/validity.go new file mode 100644 index 0000000..c398021 --- /dev/null +++ b/validity.go @@ -0,0 +1,67 @@ +package micha + +import ( + "crypto/hmac" + "crypto/sha256" + "errors" + "fmt" + "net/url" + "sort" + "strings" +) + +var ( + ErrInvalidHash = errors.New("invalid hash") +) + +// ValidateAuthCallback - https://core.telegram.org/widgets/login#checking-authorization +func ValidateAuthCallback(values url.Values, botToken string) error { + secret := sha256.Sum256([]byte(botToken)) + + return validateHash(values, secret[:]) +} + +// ValidateWabAppData - https://core.telegram.org/bots/webapps#validating-data-received-via-the-mini-app +func ValidateWabAppData(values url.Values, botToken string) error { + hm := hmac.New(sha256.New, []byte("WebAppData")) + _, err := hm.Write([]byte(botToken)) + if err != nil { + return err + } + + secret := hm.Sum(nil) + + return validateHash(values, secret) +} + +func validateHash(values url.Values, secret []byte) error { + hm := hmac.New(sha256.New, secret) + _, err := hm.Write([]byte(buildCheckString(values))) + if err != nil { + return err + } + + if fmt.Sprintf("%x", hm.Sum(nil)) != values.Get("hash") { + return ErrInvalidHash + } + + return nil +} + +func buildCheckString(values url.Values) string { + keys := []string{} + for key := range values { + if key == "hash" { + continue + } + keys = append(keys, key) + } + + sort.Strings(keys) + parts := []string{} + for _, key := range keys { + parts = append(parts, fmt.Sprintf("%s=%s", key, values.Get(key))) + } + + return strings.Join(parts, "\n") +} diff --git a/validity_test.go b/validity_test.go new file mode 100644 index 0000000..ca799a6 --- /dev/null +++ b/validity_test.go @@ -0,0 +1,43 @@ +package micha + +import ( + "net/url" + "testing" + + "github.com/stretchr/testify/require" +) + +func TestBuildCheckString(t *testing.T) { + values := url.Values{ + "id": {"12807202"}, + "first_name": {"John"}, + "username": {"doe"}, + "photo_url": {"https://t.me/i/userpic/320/a4f15041-e6a3-4cf4-9363-b0cba2f66720.jpg"}, + "auth_date": {"1722489598"}, + "hash": {"a19016198a35deb3469a2af3c5aa1f40aa71941cf14cc20e7be8c6507b147061"}, + } + + require.Equal(t, + "auth_date=1722489598\nfirst_name=John\nid=12807202\nphoto_url=https://t.me/i/userpic/320/a4f15041-e6a3-4cf4-9363-b0cba2f66720.jpg\nusername=doe", + buildCheckString(values), + ) +} + +func TestValidateHash(t *testing.T) { + values := url.Values{ + "id": {"12807202"}, + "first_name": {"John"}, + "username": {"doe"}, + "photo_url": {"https://t.me/i/userpic/320/a4f15041-e6a3-4cf4-9363-b0cba2f66720.jpg"}, + "auth_date": {"1722489598"}, + "hash": {"aba23cb08c508bd952abb2a9b3c2e9b3d6a2c8726145ccea53bd660d7995b586"}, + } + + // Test valid + err := validateHash(values, []byte("111")) + require.Nil(t, err) + + // Test invalid + err = validateHash(values, []byte("222")) + require.NotNil(t, err) +}