-
-
Notifications
You must be signed in to change notification settings - Fork 42
/
api.go
144 lines (119 loc) · 4.09 KB
/
api.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
package telegoutil
import (
"crypto/hmac"
"crypto/sha256"
"encoding/hex"
"errors"
"io"
"net/url"
"strings"
"github.com/mymmrac/telego"
ta "github.com/mymmrac/telego/telegoapi"
)
// namedReaderImpl represents simplest implementation of telegoapi.NamedReader
type namedReaderImpl struct {
reader io.Reader
name string
}
func (r namedReaderImpl) Read(p []byte) (n int, err error) {
return r.reader.Read(p)
}
func (r namedReaderImpl) Name() string {
return r.name
}
// NameReader "names" io.Reader and returns valid telegoapi.NamedReader
func NameReader(reader io.Reader, name string) ta.NamedReader {
return namedReaderImpl{
reader: reader,
name: name,
}
}
// UpdateProcessor allows you to process updates and still use updates chan.
// New updates chan will be closed when the original chan is closed.
// Warning: Deep copy of update is passed, [telego.Update.Clone] method can panic, please read its comment.
func UpdateProcessor(updates <-chan telego.Update, buffer uint, processor func(update telego.Update) telego.Update,
) <-chan telego.Update {
processedUpdates := make(chan telego.Update, buffer)
go func() {
defer close(processedUpdates)
for update := range updates {
processedUpdates <- processor(update.Clone())
}
}()
return processedUpdates
}
// WebAppSecret represents secret used to hash web app data
const WebAppSecret = "WebAppData"
// Web app data query names
const (
WebAppQueryID = "query_id"
WebAppUser = "user"
WebAppReceiver = "receiver"
WebAppChat = "chat"
WebAppStartParam = "start_param"
WebAppCanSendAfter = "can_send_after"
WebAppAuthDate = "auth_date"
WebAppHash = "hash"
)
// ValidateWebAppData validates the integrity of value provided by `window.Telegram.WebApp.initData` from web app and
// returns url.Values containing all fields that were provided
// More info: https://core.telegram.org/bots/webapps#validating-data-received-via-the-mini-app
func ValidateWebAppData(token string, data string) (url.Values, error) {
appData, err := url.ParseQuery(data)
if err != nil {
return nil, errors.New("telego: parse query: bad data")
}
hash := appData.Get(WebAppHash)
if hash == "" {
return nil, errors.New("telego: no hash found")
}
appData.Del(WebAppHash)
// Can't return error because [url.Values.Encode] method always inescapable
//nolint:errcheck
appDataToCheck, _ := url.QueryUnescape(strings.ReplaceAll(appData.Encode(), "&", "\n"))
secretKey := hmacHash([]byte(token), []byte(WebAppSecret))
if hex.EncodeToString(hmacHash([]byte(appDataToCheck), secretKey)) != hash {
return nil, errors.New("telego: invalid hash")
}
appData.Add(WebAppHash, hash)
return appData, nil
}
// Login widget data query names
const (
LoginWidgetID = "id"
LoginWidgetFirstName = "first_name"
LoginWidgetLastName = "last_name"
LoginWidgetUsername = "username"
LoginWidgetPhotoURL = "photo_url"
LoginWidgetAuthDate = "auth_date"
LoginWidgetHash = "hash"
)
// ValidateLoginWidgetData validates the integrity of value provided by Telegram Login Widget and
// returns url.Values containing all fields that were provided
// More info: https://core.telegram.org/widgets/login#checking-authorization
func ValidateLoginWidgetData(token string, data string) (url.Values, error) {
appData, err := url.ParseQuery(data)
if err != nil {
return nil, errors.New("telego: parse query: bad data")
}
hash := appData.Get(LoginWidgetHash)
if hash == "" {
return nil, errors.New("telego: no hash found")
}
appData.Del(LoginWidgetHash)
// Can't return error because [url.Values.Encode] method always inescapable
//nolint:errcheck
appDataToCheck, _ := url.QueryUnescape(strings.ReplaceAll(appData.Encode(), "&", "\n"))
secretKey := sha256.Sum256([]byte(token))
if hex.EncodeToString(hmacHash([]byte(appDataToCheck), secretKey[:])) != hash {
return nil, errors.New("telego: invalid hash")
}
appData.Add(LoginWidgetHash, hash)
return appData, nil
}
// hmacHash hashes data with a provided key using HMAC and SHA256
func hmacHash(data, key []byte) []byte {
h := hmac.New(sha256.New, key)
_, _ = h.Write(data)
return h.Sum(nil)
}