Files
15-puzzle/internal/validator/validator.go
T
Ilia Denisov 31957206b8 initial
2025-09-03 18:43:28 +03:00

68 lines
1.7 KiB
Go

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