// middleware/auth.go (обновленная версия с логированием) package middleware import ( "context" "fmt" "net/http" "strings" "api_yal/internal/logger" "github.com/golang-jwt/jwt/v5" "go.uber.org/zap" ) type contextKey string const ( UserIDKey contextKey = "userID" UserEmailKey contextKey = "userEmail" UserRoleKey contextKey = "userRole" ) // AuthMiddleware создает middleware для проверки JWT токена func AuthMiddleware(jwtSecret string) func(http.Handler) http.Handler { return func(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { l := logger.Get() l.Info("=== AUTH MIDDLEWARE START ===") l.Info("Request path", zap.String("path", r.URL.Path)) // Получаем токен из заголовка Authorization authHeader := r.Header.Get("Authorization") l.Info("Authorization header", zap.String("header", authHeader)) if authHeader == "" { l.Warn("Отсутствует заголовок Authorization") http.Error(w, "Authorization header required", http.StatusUnauthorized) return } // Ожидаем формат "Bearer " parts := strings.Split(authHeader, " ") if len(parts) != 2 || strings.ToLower(parts[0]) != "bearer" { l.Warn("Неверный формат заголовка Authorization", zap.Int("parts_count", len(parts)), zap.String("first_part", parts[0])) http.Error(w, "Invalid authorization header format", http.StatusUnauthorized) return } tokenString := parts[1] l.Info("Token extracted", zap.String("token_preview", tokenString[:min(20, len(tokenString))]+"...")) // Парсим и валидируем токен token, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) { // Проверяем метод подписи if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok { l.Error("Unexpected signing method", zap.String("method", token.Method.Alg())) return nil, jwt.ErrSignatureInvalid } return []byte(jwtSecret), nil }) if err != nil { l.Error("Token parse error", zap.Error(err)) http.Error(w, "Invalid token: "+err.Error(), http.StatusUnauthorized) return } if !token.Valid { l.Error("Token is not valid") http.Error(w, "Invalid token", http.StatusUnauthorized) return } l.Info("Token is valid") // Извлекаем claims claims, ok := token.Claims.(jwt.MapClaims) if !ok { l.Error("Failed to extract claims") http.Error(w, "Invalid token claims", http.StatusUnauthorized) return } l.Info("Claims extracted", zap.Any("claims", claims)) // Проверяем тип токена (должен быть access) if tokenType, exists := claims["type"]; exists { l.Info("Token type", zap.String("type", tokenType.(string))) if tokenType != "access" { l.Error("Wrong token type, expected access", zap.String("type", tokenType.(string))) http.Error(w, "Invalid token type", http.StatusUnauthorized) return } } // Добавляем информацию о пользователе в контекст ctx := r.Context() // Извлекаем userID из sub (subject) // В claims sub хранится как string, а не float64 if userID, ok := claims["sub"].(string); ok { l.Info("User ID from claims", zap.String("user_id_str", userID)) // Конвертируем string в uint var userIDUint uint if _, err := fmt.Sscan(userID, &userIDUint); err == nil { ctx = context.WithValue(ctx, UserIDKey, userIDUint) l.Info("User ID added to context", zap.Uint("user_id", userIDUint)) } } else { l.Error("sub claim not found or wrong type") } // Извлекаем email if email, ok := claims["email"].(string); ok { ctx = context.WithValue(ctx, UserEmailKey, email) l.Info("Email added to context", zap.String("email", email)) } // Извлекаем роль if role, ok := claims["role"].(string); ok { ctx = context.WithValue(ctx, UserRoleKey, role) l.Info("Role added to context", zap.String("role", role)) } l.Info("=== AUTH MIDDLEWARE END ===") // Передаем управление дальше с обновленным контекстом next.ServeHTTP(w, r.WithContext(ctx)) }) } } func min(a, b int) int { if a < b { return a } return b }