package gateway import ( "fmt" "log" "net/http" "net/http/httputil" "net/url" httperrors "github.com/explorer/backend/libs/go-http-errors" ) // Gateway represents the API gateway type Gateway struct { apiURL *url.URL rateLimiter *RateLimiter auth *AuthMiddleware } // NewGateway creates a new API gateway func NewGateway(apiURL string) (*Gateway, error) { parsedURL, err := url.Parse(apiURL) if err != nil { return nil, fmt.Errorf("invalid API URL: %w", err) } return &Gateway{ apiURL: parsedURL, rateLimiter: NewRateLimiter(), auth: NewAuthMiddleware(), }, nil } // Start starts the gateway server func (g *Gateway) Start(port int) error { mux := http.NewServeMux() // Proxy to API server proxy := httputil.NewSingleHostReverseProxy(g.apiURL) mux.HandleFunc("/", g.handleRequest(proxy)) addr := fmt.Sprintf(":%d", port) log.Printf("Starting API Gateway on %s", addr) return http.ListenAndServe(addr, mux) } // handleRequest handles incoming requests with middleware func (g *Gateway) handleRequest(proxy *httputil.ReverseProxy) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { // Add security headers g.addSecurityHeaders(w) // Authentication if !g.auth.Authenticate(r) { httperrors.WriteJSON(w, http.StatusUnauthorized, "UNAUTHORIZED", "Unauthorized") return } // Rate limiting if !g.rateLimiter.Allow(r) { httperrors.WriteJSON(w, http.StatusTooManyRequests, "RATE_LIMIT_EXCEEDED", "Rate limit exceeded") return } // Add headers r.Header.Set("X-Forwarded-For", r.RemoteAddr) if apiKey := g.auth.GetAPIKey(r); apiKey != "" { r.Header.Set("X-API-Key", apiKey) } // Add branding header w.Header().Set("X-Explorer-Name", "SolaceScanScout") w.Header().Set("X-Explorer-Version", "1.0.0") // Proxy request proxy.ServeHTTP(w, r) } } // addSecurityHeaders adds security headers to responses func (g *Gateway) addSecurityHeaders(w http.ResponseWriter) { w.Header().Set("X-Content-Type-Options", "nosniff") w.Header().Set("X-Frame-Options", "DENY") w.Header().Set("X-XSS-Protection", "1; mode=block") w.Header().Set("Strict-Transport-Security", "max-age=31536000; includeSubDomains") w.Header().Set("Referrer-Policy", "strict-origin-when-cross-origin") // CSP will be set per route if needed w.Header().Set("Permissions-Policy", "geolocation=(), microphone=(), camera=()") } // RateLimiter handles rate limiting type RateLimiter struct { // Simple in-memory rate limiter (should use Redis in production) limits map[string]*limitEntry } type limitEntry struct { count int resetAt int64 } func NewRateLimiter() *RateLimiter { return &RateLimiter{ limits: make(map[string]*limitEntry), } } func (rl *RateLimiter) Allow(r *http.Request) bool { _ = r.RemoteAddr // Will be used in production for per-IP limiting // In production, use Redis with token bucket algorithm // For now, simple per-IP limiting return true // Simplified - implement proper rate limiting } // AuthMiddleware handles authentication type AuthMiddleware struct { // In production, validate against database } func NewAuthMiddleware() *AuthMiddleware { return &AuthMiddleware{} } func (am *AuthMiddleware) Authenticate(r *http.Request) bool { // Allow anonymous access for now // In production, validate API key apiKey := am.GetAPIKey(r) return apiKey != "" || true // Allow anonymous for MVP } func (am *AuthMiddleware) GetAPIKey(r *http.Request) string { // Check header first if key := r.Header.Get("X-API-Key"); key != "" { return key } // Check query parameter if key := r.URL.Query().Get("api_key"); key != "" { return key } return "" }