Files
rmser/internal/transport/http/middleware/auth.go
2025-12-18 08:33:12 +03:00

130 lines
4.0 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package middleware
import (
"crypto/hmac"
"crypto/sha256"
"encoding/hex"
"fmt"
"net/http"
"net/url"
"sort"
"strconv"
"strings"
"rmser/internal/domain/account"
"github.com/gin-gonic/gin"
)
// AuthMiddleware проверяет initData от Telegram
func AuthMiddleware(accountRepo account.Repository, botToken string) gin.HandlerFunc {
return func(c *gin.Context) {
// 1. Извлекаем данные авторизации
authHeader := c.GetHeader("Authorization")
var initData string
if strings.HasPrefix(authHeader, "Bearer ") {
initData = strings.TrimPrefix(authHeader, "Bearer ")
} else {
// Оставляем лазейку для отладки ТОЛЬКО если это не production режим
// В реальности лучше всегда требовать подпись
initData = c.Query("_auth")
}
if initData == "" {
c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"error": "Авторизация отклонена: отсутствует подпись Telegram"})
return
}
// 2. Проверяем подпись (HMAC-SHA256)
isValid, err := verifyTelegramInitData(initData, botToken)
if !isValid || err != nil {
c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"error": "Критическая ошибка безопасности: поддельная подпись"})
return
}
// 3. Извлекаем User ID из проверенных данных
values, _ := url.ParseQuery(initData)
userJSON := values.Get("user")
// Извлекаем id вручную из JSON-подобной строки или через простой парсинг
// Telegram передает user как JSON-объект: {"id":12345,"first_name":"..."}
tgID, err := extractIDFromUserJSON(userJSON)
if err != nil {
c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"error": "Не удалось извлечь Telegram ID"})
return
}
// 4. Ищем пользователя в БД
user, err := accountRepo.GetUserByTelegramID(tgID)
if err != nil {
c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"error": "Пользователь не зарегистрирован. Начните диалог с ботом."})
return
}
c.Set("userID", user.ID)
c.Set("telegramID", tgID)
c.Next()
}
}
// verifyTelegramInitData реализует алгоритм проверки Telegram
func verifyTelegramInitData(initData, token string) (bool, error) {
values, err := url.ParseQuery(initData)
if err != nil {
return false, err
}
hash := values.Get("hash")
if hash == "" {
return false, fmt.Errorf("no hash found")
}
values.Del("hash")
// Сортируем ключи
keys := make([]string, 0, len(values))
for k := range values {
keys = append(keys, k)
}
sort.Strings(keys)
// Собираем data_check_string
var dataCheckArr []string
for _, k := range keys {
dataCheckArr = append(dataCheckArr, fmt.Sprintf("%s=%s", k, values.Get(k)))
}
dataCheckString := strings.Join(dataCheckArr, "\n")
// Вычисляем секретный ключ: HMAC-SHA256("WebAppData", token)
sha := sha256.New()
sha.Write([]byte(token))
secretKey := hmac.New(sha256.New, []byte("WebAppData"))
secretKey.Write([]byte(token))
// Вычисляем финальный HMAC
h := hmac.New(sha256.New, secretKey.Sum(nil))
h.Write([]byte(dataCheckString))
expectedHash := hex.EncodeToString(h.Sum(nil))
return expectedHash == hash, nil
}
// Упрощенное извлечение ID из JSON-строки поля user
func extractIDFromUserJSON(userJSON string) (int64, error) {
// Ищем "id":(\d+)
// Для надежности в будущем можно использовать json.Unmarshal
startIdx := strings.Index(userJSON, "\"id\":")
if startIdx == -1 {
return 0, fmt.Errorf("id not found")
}
startIdx += 5
endIdx := strings.IndexAny(userJSON[startIdx:], ",}")
if endIdx == -1 {
return 0, fmt.Errorf("invalid json")
}
idStr := userJSON[startIdx : startIdx+endIdx]
return strconv.ParseInt(idStr, 10, 64)
}