/
theosov
/
Radiola
Обзор
Документация
Войти
/
theosov
/
Radiola
Код
Запросы
0
Задачи
Вики
Пакеты
0
Релизы
0
CI/CD
Аналитика
Безопасность
master
internal/auth/initdata.go
109 строк
3 KB
Theosov
P0
25 июл 2026, 01:31
25 июл 2026, 01:31
1f4159e
Код
Авторство
О чём код?
package auth import ( "crypto/hmac" "crypto/sha256" "encoding/hex" "encoding/json" "fmt" "net/url" "sort" "strconv" "strings" "time" "github.com/theosov/radiola/internal/config" ) // ValidateResult holds the authenticated Telegram user info. type ValidateResult struct { UserID int64 User TelegramUser } // TelegramUser is the subset of the initData user field we need. type TelegramUser struct { ID int64 `json:"id"` } // Validate checks X-Telegram-Init-Data header value against bot_token and whitelist. func Validate(initData string, cfg *config.Config) (ValidateResult, error) { if initData == "" { return ValidateResult{}, &AuthError{Code: "unauthorized", Message: "missing initData"} } values, err := url.ParseQuery(initData) if err != nil { return ValidateResult{}, &AuthError{Code: "unauthorized", Message: "invalid initData format"} } hash := values.Get("hash") values.Del("hash") if hash == "" { return ValidateResult{}, &AuthError{Code: "unauthorized", Message: "missing hash"} } // Build data-check-string with keys sorted alphabetically. keys := make([]string, 0, len(values)) for k := range values { keys = append(keys, k) } sort.Strings(keys) pairs := make([]string, 0, len(keys)) for _, k := range keys { pairs = append(pairs, fmt.Sprintf("%s=%s", k, values.Get(k))) } dataCheckString := strings.Join(pairs, "\n") secret := hmacSHA256("WebAppData", cfg.Server.BotToken) expected := hmacSHA256Hex(secret, dataCheckString) if !hmac.Equal([]byte(expected), []byte(hash)) { return ValidateResult{}, &AuthError{Code: "unauthorized", Message: "invalid hash"} } authDate, err := strconv.ParseInt(values.Get("auth_date"), 10, 64) if err != nil { return ValidateResult{}, &AuthError{Code: "unauthorized", Message: "missing auth_date"} } if time.Since(time.Unix(authDate, 0)) > config.AuthTimeout { return ValidateResult{}, &AuthError{Code: "unauthorized", Message: "initData expired"} } userRaw := values.Get("user") if userRaw == "" { return ValidateResult{}, &AuthError{Code: "unauthorized", Message: "missing user"} } var user TelegramUser if err := json.Unmarshal([]byte(userRaw), &user); err != nil { return ValidateResult{}, &AuthError{Code: "unauthorized", Message: "invalid user"} } allowed := make(map[int64]struct{}, len(cfg.Server.AllowedUserIDs)) for _, id := range cfg.Server.AllowedUserIDs { allowed[id] = struct{}{} } if _, ok := allowed[user.ID]; !ok { return ValidateResult{}, &AuthError{Code: "forbidden", Message: "user not allowed"} } return ValidateResult{UserID: user.ID, User: user}, nil } // AuthError is returned for auth-related failures with an API error code. type AuthError struct { Code string Message string } func (e *AuthError) Error() string { return e.Message } func hmacSHA256(key, data string) []byte { h := hmac.New(sha256.New, []byte(key)) _, _ = h.Write([]byte(data)) return h.Sum(nil) } func hmacSHA256Hex(key []byte, data string) string { h := hmac.New(sha256.New, key) _, _ = h.Write([]byte(data)) return hex.EncodeToString(h.Sum(nil)) }