package handlers import ( "context" "fmt" "io" "log" "net/http" "strings" "aggios-app/backend/internal/config" "github.com/gorilla/mux" "github.com/minio/minio-go/v7" "github.com/minio/minio-go/v7/pkg/credentials" ) type FilesHandler struct { config *config.Config } func NewFilesHandler(cfg *config.Config) *FilesHandler { return &FilesHandler{ config: cfg, } } // ServeFile serves files from MinIO through the API func (h *FilesHandler) ServeFile(w http.ResponseWriter, r *http.Request) { vars := mux.Vars(r) bucket := vars["bucket"] // Get the file path (everything after /api/files/{bucket}/) prefix := fmt.Sprintf("/api/files/%s/", bucket) filePath := strings.TrimPrefix(r.URL.Path, prefix) if filePath == "" { http.Error(w, "File path is required", http.StatusBadRequest) return } // Whitelist de buckets públicos permitidos allowedBuckets := map[string]bool{ "aggios-logos": true, } if !allowedBuckets[bucket] { log.Printf("🚫 Access denied to bucket: %s", bucket) http.Error(w, "Access denied", http.StatusForbidden) return } // Proteção contra path traversal if strings.Contains(filePath, "..") { log.Printf("🚫 Path traversal attempt detected: %s", filePath) http.Error(w, "Invalid path", http.StatusBadRequest) return } log.Printf("📁 Serving file: bucket=%s, path=%s", bucket, filePath) // Initialize MinIO client minioClient, err := minio.New("aggios-minio:9000", &minio.Options{ Creds: credentials.NewStaticV4("minioadmin", "M1n10_S3cur3_P@ss_2025!", ""), Secure: false, }) if err != nil { log.Printf("Failed to create MinIO client: %v", err) http.Error(w, "Storage service unavailable", http.StatusInternalServerError) return } // Get object from MinIO ctx := context.Background() object, err := minioClient.GetObject(ctx, bucket, filePath, minio.GetObjectOptions{}) if err != nil { log.Printf("Failed to get object: %v", err) http.Error(w, "File not found", http.StatusNotFound) return } defer object.Close() // Get object info for content type and size objInfo, err := object.Stat() if err != nil { log.Printf("Failed to stat object: %v", err) http.Error(w, "File not found", http.StatusNotFound) return } // Set appropriate headers w.Header().Set("Content-Type", objInfo.ContentType) w.Header().Set("Content-Length", fmt.Sprintf("%d", objInfo.Size)) w.Header().Set("Cache-Control", "public, max-age=31536000") // Cache for 1 year w.Header().Set("Access-Control-Allow-Origin", "*") // Copy file content to response _, err = io.Copy(w, object) if err != nil { log.Printf("Failed to copy object content: %v", err) return } log.Printf("✅ File served successfully: %s", filePath) }