package middleware import ( "net/http" "strings" ) // CORS returns a middleware that adds Cross-Origin Resource Sharing headers to // every response and handles OPTIONS preflight requests. It is designed for the // Veylant dashboard (React SPA) which calls the proxy from a different origin. // // allowedOrigins is the list of permitted origins (e.g. ["http://localhost:3000", // "https://dashboard.veylant.ai"]). An empty list disables CORS (no headers added). // Pass ["*"] to allow any origin (development only — never use in production). func CORS(allowedOrigins []string) func(http.Handler) http.Handler { if len(allowedOrigins) == 0 { // No origins configured — identity middleware. return func(next http.Handler) http.Handler { return next } } originSet := make(map[string]struct{}, len(allowedOrigins)) wildcard := false for _, o := range allowedOrigins { if o == "*" { wildcard = true } originSet[o] = struct{}{} } return func(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { origin := r.Header.Get("Origin") // Only set CORS headers when the request carries an Origin header. if origin != "" { _, allowed := originSet[origin] if wildcard || allowed { w.Header().Set("Access-Control-Allow-Origin", origin) w.Header().Set("Access-Control-Allow-Credentials", "true") w.Header().Add("Vary", "Origin") } } // Handle preflight (OPTIONS) — respond 204 and do not forward. if r.Method == http.MethodOptions && origin != "" { requestedMethod := r.Header.Get("Access-Control-Request-Method") requestedHeaders := r.Header.Get("Access-Control-Request-Headers") if requestedMethod != "" { w.Header().Set("Access-Control-Allow-Methods", "GET, POST, PUT, PATCH, DELETE, OPTIONS") if requestedHeaders != "" { // Echo the requested headers back (normalised to title-case). w.Header().Set("Access-Control-Allow-Headers", strings.ToTitle(requestedHeaders)) } else { w.Header().Set("Access-Control-Allow-Headers", "Authorization, Content-Type, X-Request-Id") } w.Header().Set("Access-Control-Max-Age", "86400") // 24 h preflight cache w.WriteHeader(http.StatusNoContent) return } } next.ServeHTTP(w, r) }) } }