This commit is contained in:
Ilia Denisov
2025-09-03 18:43:28 +03:00
commit 31957206b8
40 changed files with 2947 additions and 0 deletions
+67
View File
@@ -0,0 +1,67 @@
package validator
import (
"crypto/hmac"
"crypto/sha256"
"encoding/hex"
"encoding/json"
"fmt"
"maps"
"net/url"
"slices"
"strings"
)
// ValidUser returns validation result of initData with key used for hashing.
// If validation was successfull then value of boolean is true and initData's user.id value is returned as int.
// Otherwise, including when error is not nil, the returned boolean will be false and int will be zero.
//
// Implemented according to https://core.telegram.org/bots/webapps#validating-data-received-via-the-mini-app
func ValidUser(key []byte, initData string) (bool, int, error) {
m, err := url.ParseQuery(initData)
if err != nil {
return false, 0, fmt.Errorf("parse init data %q: %s", initData, err)
}
var hash []byte
if v, ok := m["hash"]; ok && len(v) > 0 {
hash, err = hex.DecodeString(v[0])
if err != nil {
return false, 0, fmt.Errorf("decode original hash %q: %s", v[0], err)
}
delete(m, "hash")
} else {
return false, 0, nil
}
keys := slices.Collect(maps.Keys(m))
slices.Sort(keys)
u := new(struct {
ID *int `json:"id"`
})
fields := make([]string, 0)
for _, k := range keys {
if v, ok := m[k]; ok && len(v) == 1 {
if k == "user" {
if err := json.Unmarshal([]byte(v[0]), &u); err != nil {
return false, 0, fmt.Errorf("unmarshall user data %s: %s", v[0], err)
}
if u.ID == nil {
return false, 0, fmt.Errorf("user.id field not set: %s: ", v[0])
}
}
fields = append(fields, k+"="+v[0])
}
}
if slices.Equal(EncodeHmacSha256([]byte(strings.Join(fields, "\n")), key), hash) {
return true, *u.ID, nil
}
return false, 0, nil
}
func EncodeHmacSha256(data, key []byte) []byte {
sig := hmac.New(sha256.New, key)
sig.Write(data)
return sig.Sum(nil)
}
+28
View File
@@ -0,0 +1,28 @@
package validator_test
import (
"15-puzzle/internal/validator"
"testing"
"github.com/stretchr/testify/assert"
)
const (
botToken = "example:token"
initData = "auth_date=269666017&chat_instance=6039284203686499081&chat_type=sender&hash=40cde8dc7250ee616cd7d7a090749a9a42cc68f018c969fcb48fdf5e62657ad6&signature=FF5oTJSnmxdqgtNozxsLywXyVKdssh_DbvksGUaQuhkMiRfp10HJmf5o88uokPpqF4yhpHbX1c8uLbrKUuUdAA&user=%7B%22allows_write_to_pm%22%3Atrue%2C%22first_name%22%3A%22Ilia%22%2C%22id%22%3A303133707%2C%22is_premium%22%3Atrue%2C%22language_code%22%3A%22en%22%2C%22last_name%22%3A%22Denisov%22%2C%22photo_url%22%3A%22https%3A%2F%2Fyoutu.be%2FdQw4w9WgXcQ%22%7D"
userId = 303133707
)
func TestValidUser(t *testing.T) {
valid, id, err := validator.ValidUser(validator.EncodeHmacSha256([]byte(botToken), []byte("WebAppData")), initData)
assert.NoError(t, err)
assert.True(t, valid)
assert.Equal(t, userId, id)
valid, _, err = validator.ValidUser(validator.EncodeHmacSha256([]byte(botToken), []byte("Web_AppData")), initData)
assert.NoError(t, err)
assert.False(t, valid)
_, _, err = validator.ValidUser(validator.EncodeHmacSha256([]byte(botToken), []byte("WebAppData")), "param;=value&")
assert.Error(t, err)
}