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, maintenanceMode bool, devIDs []int64) 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 } // Проверка режима обслуживания: если включен, разрешаем доступ только разработчикам if maintenanceMode { isDev := false for _, devID := range devIDs { if tgID == devID { isDev = true break } } if !isDev { c.AbortWithStatusJSON(503, gin.H{"error": "maintenance_mode", "message": "Сервис на обслуживании"}) 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) }