авторизация fixed

This commit is contained in:
2025-12-18 08:33:12 +03:00
parent 4e4571b3db
commit b5b9504019
6 changed files with 161 additions and 293 deletions

View File

@@ -1,49 +1,129 @@
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 извлекает Telegram User ID и находит User UUID
func AuthMiddleware(accountRepo account.Repository) gin.HandlerFunc {
// AuthMiddleware проверяет initData от Telegram
func AuthMiddleware(accountRepo account.Repository, botToken string) gin.HandlerFunc {
return func(c *gin.Context) {
// 1. Ищем в заголовке (стандартный путь)
tgIDStr := c.GetHeader("X-Telegram-User-ID")
// 1. Извлекаем данные авторизации
authHeader := c.GetHeader("Authorization")
var initData string
// 2. Если нет в заголовке, ищем в Query (для отладки в браузере)
// Пример: /api/drafts?_tg_id=12345678
if tgIDStr == "" {
tgIDStr = c.Query("_tg_id")
if strings.HasPrefix(authHeader, "Bearer ") {
initData = strings.TrimPrefix(authHeader, "Bearer ")
} else {
// Оставляем лазейку для отладки ТОЛЬКО если это не production режим
// В реальности лучше всегда требовать подпись
initData = c.Query("_auth")
}
if tgIDStr == "" {
c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"error": "Missing X-Telegram-User-ID header or _tg_id param"})
if initData == "" {
c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"error": "Авторизация отклонена: отсутствует подпись Telegram"})
return
}
tgID, err := strconv.ParseInt(tgIDStr, 10, 64)
// 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.StatusBadRequest, gin.H{"error": "Invalid Telegram ID"})
c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"error": "Не удалось извлечь Telegram ID"})
return
}
// Ищем пользователя в БД
// 4. Ищем пользователя в БД
user, err := accountRepo.GetUserByTelegramID(tgID)
if err != nil {
// Если пользователя нет - значит он не нажал /start в боте
c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"error": "User not registered via Bot. Please start the bot first."})
c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"error": ользователь не зарегистрирован. Начните диалог с ботом."})
return
}
// Кладем UUID пользователя в контекст
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)
}