init: 1.0.0
This commit is contained in:
@@ -0,0 +1,60 @@
|
||||
package telegram
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// ParseAuthDataJson parses provided json content for AuthData
|
||||
func ParseAuthDataJson(content []byte) (*AuthData, error) {
|
||||
data := &AuthData{}
|
||||
err := json.Unmarshal(content, data)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unmarshaling error: %w", err)
|
||||
}
|
||||
return data, nil
|
||||
}
|
||||
|
||||
// ParseAuthDataBase64 decodes provided content from base64 and parses result for AuthData
|
||||
func ParseAuthDataBase64(content []byte) (*AuthData, error) {
|
||||
|
||||
decodedBytes, err := base64.RawStdEncoding.DecodeString(string(content))
|
||||
if err != nil && len(decodedBytes) == 0 {
|
||||
return nil, fmt.Errorf("base64 decoding error: %w", err)
|
||||
}
|
||||
return ParseAuthDataJson(decodedBytes)
|
||||
}
|
||||
|
||||
// ParseAndValidateBase64 parses base64 content for AuthData and validates it
|
||||
func ParseAndValidateBase64(content []byte, botToken string) (*AuthData, error) {
|
||||
authData, err := ParseAuthDataBase64(content)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = authData.Validate([]byte(botToken))
|
||||
return authData, err
|
||||
}
|
||||
|
||||
// ParseAndValidateJson parses json content for AuthData and validates it
|
||||
func ParseAndValidateJson(content []byte, botToken []byte) (*AuthData, error) {
|
||||
authData, err := ParseAuthDataJson(content)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = authData.Validate(botToken)
|
||||
return authData, err
|
||||
}
|
||||
|
||||
// GenerateTelegramOAuthURL generates a URL for Telegram OAuth
|
||||
func GenerateTelegramOAuthURL(botToken, embed, redirect string) string {
|
||||
bot := strings.Split(botToken, ":")
|
||||
uri := "https://oauth.telegram.org/auth?bot_id=%s&origin=%s&embed=%s&request_access=write&return_to=%s"
|
||||
parsedURL, err := url.Parse(redirect)
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
return fmt.Sprintf(uri, bot[0], fmt.Sprintf("%s://%s", parsedURL.Scheme, parsedURL.Host), embed, redirect)
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
package telegram
|
||||
|
||||
import (
|
||||
"crypto/hmac"
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"sort"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// getAuthDataCheckString returns a string ready to calculate validation hash.
|
||||
// Ref: https://core.telegram.org/widgets/login#checking-authorization
|
||||
func getAuthDataCheckString(data *AuthData) string {
|
||||
t := reflect.TypeOf(data).Elem()
|
||||
v := reflect.ValueOf(data).Elem()
|
||||
fields := make([]string, 0)
|
||||
for i := 0; i < t.NumField(); i++ {
|
||||
tag, _, _ := strings.Cut(t.Field(i).Tag.Get("json"), ",")
|
||||
if v.Field(i).IsNil() || tag == "hash" {
|
||||
continue
|
||||
}
|
||||
val := v.Field(i).Elem().Interface()
|
||||
if val == nil || val == "null" {
|
||||
continue
|
||||
}
|
||||
fields = append(fields, fmt.Sprintf("%s=%v", tag, val))
|
||||
}
|
||||
sort.Strings(fields)
|
||||
return strings.Join(fields, "\n")
|
||||
}
|
||||
|
||||
// computeHash returns a hash calculated for AuthData
|
||||
// Ref: https://core.telegram.org/widgets/login#checking-authorization
|
||||
func computeHash(data *AuthData, botToken []byte) string {
|
||||
checkString := getAuthDataCheckString(data)
|
||||
key := sha256.Sum256(botToken)
|
||||
h := hmac.New(sha256.New, key[:])
|
||||
h.Write([]byte(checkString))
|
||||
return hex.EncodeToString(h.Sum(nil))
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
package telegram
|
||||
|
||||
import "fmt"
|
||||
|
||||
type AuthData struct {
|
||||
Id *int64 `json:"id,omitempty"`
|
||||
FirstName *string `json:"first_name,omitempty"`
|
||||
LastName *string `json:"last_name,omitempty"`
|
||||
Username *string `json:"username,omitempty"`
|
||||
PhotoUrl *string `json:"photo_url,omitempty"`
|
||||
AuthDate *int64 `json:"auth_date,omitempty"`
|
||||
Hash *string `json:"hash,omitempty"`
|
||||
}
|
||||
|
||||
// Validate checks the hash of AuthData with computed one. To compute hash botToken is required.
|
||||
// Ref: https://core.telegram.org/widgets/login#checking-authorization
|
||||
func (d *AuthData) Validate(botToken []byte) error {
|
||||
if d.Hash == nil {
|
||||
return fmt.Errorf("auth data has no 'hash' value")
|
||||
}
|
||||
if len(botToken) == 0 {
|
||||
return fmt.Errorf("telegram bot token is not provided")
|
||||
}
|
||||
hash := *d.Hash
|
||||
computedHash := computeHash(d, botToken)
|
||||
if hash != computedHash {
|
||||
return fmt.Errorf("hash is not valid")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Telegram OAuth Test</title>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Telegram OAuth Test</h1>
|
||||
<script async src="https://telegram.org/js/telegram-widget.js?22" data-telegram-login="ppanel_dev_bot"
|
||||
data-size="large" data-auth-url="/auth/telegram/callback" data-request-access="write"></script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,36 @@
|
||||
package telegram
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"testing"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
func TestOAuth(t *testing.T) {
|
||||
t.Skipf("Skip TestOAuth test")
|
||||
router := gin.Default()
|
||||
router.LoadHTMLGlob("./*")
|
||||
router.GET("/telegram", func(c *gin.Context) {
|
||||
c.HTML(http.StatusOK, "telegram.html", gin.H{
|
||||
"title": "Gin HTML Example",
|
||||
"message": "Hello, Gin!",
|
||||
})
|
||||
})
|
||||
router.GET("/auth/telegram/callback", func(c *gin.Context) {
|
||||
|
||||
})
|
||||
_ = router.RunTLS(":443", "server.crt", "server.key")
|
||||
}
|
||||
|
||||
func TestBase64(t *testing.T) {
|
||||
text := "eyJpZCI6ODI0NjI2ODAzLCJmaXJzdF9uYW1lIjoiQ2hhbmcgbHVlIiwibGFzdF9uYW1lIjoiVHNlbiIsInVzZXJuYW1lIjoidGVuc2lvbl9jIiwicGhvdG9fdXJsIjoiaHR0cHM6XC9cL3QubWVcL2lcL3VzZXJwaWNcLzMyMFwvYU1LNkhEc0pqc2V1YldRYmt2NGlYOHZCRUF6N0hWU3g3dkFuRDBLZ0tFVS5qcGciLCJhdXRoX2RhdGUiOjE3Mzc4MTkwNzQsImhhc2giOiI5M2I1ZDg3Zjc3NjE2YjBjMTM0OTAxYmYwMDg3MTc4YjJiYmZlYzA1MTlkMWVmMDJhZjFjMGNlOTAzM2ZiNGFlIn0"
|
||||
var token = "7651491571:AAEVQma6niHhtqEYDowAEpPo6Fq69BWvRU8"
|
||||
|
||||
data, err := ParseAndValidateBase64([]byte(text), token)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
t.Log(*data.Id)
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user