package middleware import ( "context" "log" "net/http" "strings" "aggios-app/backend/internal/config" "github.com/golang-jwt/jwt/v5" ) type contextKey string const UserIDKey contextKey = "userID" const TenantIDKey contextKey = "tenantID" // Auth validates JWT tokens func Auth(cfg *config.Config) func(http.Handler) http.Handler { return func(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { authHeader := r.Header.Get("Authorization") if authHeader == "" { http.Error(w, "Unauthorized", http.StatusUnauthorized) return } bearerToken := strings.Split(authHeader, " ") if len(bearerToken) != 2 || bearerToken[0] != "Bearer" { http.Error(w, "Invalid token format", http.StatusUnauthorized) return } token, err := jwt.Parse(bearerToken[1], func(token *jwt.Token) (interface{}, error) { return []byte(cfg.JWT.Secret), nil }) if err != nil || !token.Valid { http.Error(w, "Invalid token", http.StatusUnauthorized) return } claims, ok := token.Claims.(jwt.MapClaims) if !ok { http.Error(w, "Invalid token claims", http.StatusUnauthorized) return } // Verificar se user_id existe e é do tipo correto userIDClaim, ok := claims["user_id"] if !ok || userIDClaim == nil { http.Error(w, "Missing user_id in token", http.StatusUnauthorized) return } userID, ok := userIDClaim.(string) if !ok { http.Error(w, "Invalid user_id format in token", http.StatusUnauthorized) return } // tenant_id pode ser nil para SuperAdmin var tenantIDFromJWT string if tenantIDClaim, ok := claims["tenant_id"]; ok && tenantIDClaim != nil { tenantIDFromJWT, _ = tenantIDClaim.(string) } // VALIDAÇÃO DE SEGURANÇA: Verificar user_type para impedir clientes de acessarem rotas de agência if userTypeClaim, ok := claims["user_type"]; ok && userTypeClaim != nil { userType, _ := userTypeClaim.(string) if userType == "customer" { log.Printf("❌ CUSTOMER ACCESS BLOCKED: Customer %s tried to access agency route %s", userID, r.RequestURI) http.Error(w, "Forbidden: Customers cannot access agency routes", http.StatusForbidden) return } } // VALIDAÇÃO DE SEGURANÇA: Verificar se o tenant_id do JWT corresponde ao subdomínio acessado // Pegar o tenant_id do contexto (detectado pelo TenantDetector middleware ANTES deste) tenantIDFromContext := "" if ctxTenantID := r.Context().Value(TenantIDKey); ctxTenantID != nil { tenantIDFromContext, _ = ctxTenantID.(string) } log.Printf("🔐 AUTH VALIDATION: JWT tenant=%s | Context tenant=%s | Path=%s", tenantIDFromJWT, tenantIDFromContext, r.RequestURI) // Se o usuário não é SuperAdmin (tem tenant_id) e está acessando uma agência (subdomain detectado) if tenantIDFromJWT != "" && tenantIDFromContext != "" { // Validar se o tenant_id do JWT corresponde ao tenant detectado if tenantIDFromJWT != tenantIDFromContext { log.Printf("❌ CROSS-TENANT ACCESS BLOCKED: User from tenant %s tried to access tenant %s", tenantIDFromJWT, tenantIDFromContext) http.Error(w, "Forbidden: You don't have access to this tenant", http.StatusForbidden) return } log.Printf("✅ TENANT VALIDATION PASSED: %s", tenantIDFromJWT) } // Preservar TODOS os valores do contexto anterior (incluindo o tenantID do TenantDetector) ctx := r.Context() ctx = context.WithValue(ctx, UserIDKey, userID) // Só sobrescrever o TenantIDKey se vier do JWT (para não perder o do TenantDetector) if tenantIDFromJWT != "" { ctx = context.WithValue(ctx, TenantIDKey, tenantIDFromJWT) } next.ServeHTTP(w, r.WithContext(ctx)) }) } }