124 lines
2.9 KiB
Go
124 lines
2.9 KiB
Go
package login
|
|
|
|
import (
|
|
"crypto/hmac"
|
|
"crypto/sha256"
|
|
"encoding/hex"
|
|
"net/url"
|
|
"strings"
|
|
|
|
http "github.com/valyala/fasthttp"
|
|
"golang.org/x/text/language"
|
|
)
|
|
|
|
type (
|
|
Config struct {
|
|
// ClientSecret is the bot token.
|
|
ClientSecret string
|
|
|
|
// RedirectURL is the URL to redirect users going through the login flow.
|
|
RedirectURL string
|
|
|
|
// RequestWriteAccess request the permission for bot to send messages to the user.
|
|
RequestWriteAccess bool
|
|
}
|
|
|
|
// User contains data about authenticated user.
|
|
User struct {
|
|
AuthDate int64 `json:"auth_date"`
|
|
FirstName string `json:"first_name"`
|
|
Hash string `json:"hash"`
|
|
ID int `json:"id"`
|
|
LastName string `json:"last_name,omitempty"`
|
|
PhotoURL string `json:"photo_url,omitempty"`
|
|
Username string `json:"username,omitempty"`
|
|
}
|
|
)
|
|
|
|
const Endpoint string = "https://oauth.telegram.org/auth"
|
|
|
|
// Key represents available and supported query arguments keys.
|
|
const (
|
|
KeyAuthDate string = "auth_date"
|
|
KeyFirstName string = "first_name"
|
|
KeyHash string = "hash"
|
|
KeyID string = "id"
|
|
KeyLastName string = "last_name"
|
|
KeyPhotoURL string = "photo_url"
|
|
KeyUsername string = "username"
|
|
)
|
|
|
|
// ClientID returns bot ID from it's ClientSecret token.
|
|
func (c Config) ClientID() string {
|
|
return strings.SplitN(c.ClientSecret, ":", 2)[0]
|
|
}
|
|
|
|
// AuthCodeURL returns a URL to Telegram login page that asks for permissions for the required scopes explicitly.
|
|
func (c *Config) AuthCodeURL(lang language.Tag) string {
|
|
origin, _ := url.Parse(c.RedirectURL)
|
|
|
|
u := http.AcquireURI()
|
|
defer http.ReleaseURI(u)
|
|
u.Update(Endpoint)
|
|
|
|
a := u.QueryArgs()
|
|
a.Set("bot_id", c.ClientID())
|
|
a.Set("origin", origin.Scheme+"://"+origin.Host)
|
|
a.Add("embed", "0")
|
|
|
|
if lang != language.Und {
|
|
a.Set("lang", lang.String())
|
|
}
|
|
|
|
if c.RequestWriteAccess {
|
|
a.Set("request_access", "write")
|
|
}
|
|
|
|
return u.String()
|
|
}
|
|
|
|
// Verify verify the authentication and the integrity of the data received by
|
|
// comparing the received hash parameter with the hexadecimal representation
|
|
// of the HMAC-SHA-256 signature of the data-check-string with the SHA256
|
|
// hash of the bot's token used as a secret key.
|
|
func (c *Config) Verify(u *User) bool {
|
|
if u == nil || u.Hash == "" {
|
|
return false
|
|
}
|
|
|
|
h, err := generateHash(c.ClientSecret, u)
|
|
|
|
return err == nil && u.Hash == h
|
|
}
|
|
|
|
func generateHash(token string, u *User) (string, error) {
|
|
a := http.AcquireArgs()
|
|
defer http.ReleaseArgs(a)
|
|
|
|
// WARN(toby3d): do not change order of this args, it must be alphabetical
|
|
a.SetUint(KeyAuthDate, int(u.AuthDate))
|
|
a.Set(KeyFirstName, u.FirstName)
|
|
a.SetUint(KeyID, u.ID)
|
|
|
|
if u.LastName != "" {
|
|
a.Set(KeyLastName, u.LastName)
|
|
}
|
|
|
|
if u.PhotoURL != "" {
|
|
a.Set(KeyPhotoURL, u.PhotoURL)
|
|
}
|
|
|
|
if u.Username != "" {
|
|
a.Set(KeyUsername, u.Username)
|
|
}
|
|
|
|
secretKey := sha256.Sum256([]byte(token))
|
|
h := hmac.New(sha256.New, secretKey[0:])
|
|
|
|
if _, err := h.Write(a.QueryString()); err != nil {
|
|
return "", err
|
|
}
|
|
|
|
return hex.EncodeToString(h.Sum(nil)), nil
|
|
}
|