initial
This commit is contained in:
@@ -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)
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
Reference in New Issue
Block a user