diff --git a/README.md b/README.md index 49aed32..4aa6dd0 100644 --- a/README.md +++ b/README.md @@ -339,6 +339,23 @@ p := &bot.SendPollParams{ b.SendPoll(ctx, p) ``` +### `ValidateWebappRequest(values url.Values, token string) (user *models.User, ok bool)` + +Validate request from Telegram Webapp + +https://core.telegram.org/bots/webapps#validating-data-received-via-the-mini-app + +```go +// get url values from request +values := req.URL.Query() + +user, ok := bot.ValidateWebappRequest(values, os.Getenv("TELEGRAM_BOT_TOKEN")) +if !ok { + http.Error(w, "Unauthorized", http.StatusUnauthorized) + return +} +``` + ### `FileDownloadLink(f *models.File) string` Return file download link after call method `GetFile` diff --git a/common.go b/common.go index c2c1a99..3f28a11 100644 --- a/common.go +++ b/common.go @@ -1,10 +1,18 @@ package bot import ( + "crypto/hmac" + "crypto/sha256" + "encoding/json" + "fmt" "math/rand" + "net/url" + "sort" "strings" "sync" "time" + + "github.com/go-telegram/bot/models" ) // escape special symbols in text for MarkdownV2 parse mode @@ -80,3 +88,44 @@ func RandomString(n int) string { return string(b) } + +// ValidateWebappRequest validates request from webapp +func ValidateWebappRequest(values url.Values, token string) (user *models.User, ok bool) { + h := values.Get("hash") + values.Del("hash") + + var vals []string + + var u models.User + + for k, v := range values { + vv, _ := url.QueryUnescape(v[0]) + vals = append(vals, k+"="+vv) + if k == "user" { + errDecodeUser := json.Unmarshal([]byte(vv), &u) + if errDecodeUser != nil { + return nil, false + } + } + } + + sort.Slice(vals, func(i, j int) bool { + return vals[i] < vals[j] + }) + + hmac1 := hmac.New(sha256.New, []byte("WebAppData")) + hmac1.Write([]byte(token)) + r1 := hmac1.Sum(nil) + + data := []byte(strings.Join(vals, "\n")) + + hmac2 := hmac.New(sha256.New, r1) + hmac2.Write(data) + r2 := hmac2.Sum(nil) + + if h != fmt.Sprintf("%x", r2) { + return nil, false + } + + return &u, true +}