From 82f0927f007e23705463e0e0a76a57f36810d6c8 Mon Sep 17 00:00:00 2001 From: vissersg Date: Tue, 24 Mar 2026 12:57:24 -0400 Subject: [PATCH 1/2] fix: pass allowed methods to CORS middleware --- internal/dev_server/sdk/cors.go | 20 +++++--- internal/dev_server/sdk/cors_test.go | 74 ++++++++++++++++++++++++++++ internal/dev_server/sdk/routes.go | 15 +++--- 3 files changed, 95 insertions(+), 14 deletions(-) create mode 100644 internal/dev_server/sdk/cors_test.go diff --git a/internal/dev_server/sdk/cors.go b/internal/dev_server/sdk/cors.go index dffd7dc0..7ffdf1b2 100644 --- a/internal/dev_server/sdk/cors.go +++ b/internal/dev_server/sdk/cors.go @@ -1,17 +1,21 @@ package sdk import ( + "net/http" + "github.com/gorilla/handlers" ) -var CorsHeaders = handlers.CORS( - handlers.AllowedOrigins([]string{"*"}), - handlers.AllowedMethods([]string{"GET"}), - handlers.AllowCredentials(), - handlers.ExposedHeaders([]string{"Date"}), - handlers.AllowedHeaders([]string{"Cache-Control", "Content-Type", "Content-Length", "Accept-Encoding", "X-LaunchDarkly-Event-Schema", "X-LaunchDarkly-User-Agent", "X-LaunchDarkly-Payload-ID", "X-LaunchDarkly-Wrapper", "X-LaunchDarkly-Tags"}), - handlers.MaxAge(300), -) +func CorsHeadersForMethods(methods ...string) func(http.Handler) http.Handler { + return handlers.CORS( + handlers.AllowedOrigins([]string{"*"}), + handlers.AllowedMethods(methods), + handlers.AllowCredentials(), + handlers.ExposedHeaders([]string{"Date"}), + handlers.AllowedHeaders([]string{"Cache-Control", "Content-Type", "Content-Length", "Accept-Encoding", "X-LaunchDarkly-Event-Schema", "X-LaunchDarkly-User-Agent", "X-LaunchDarkly-Payload-ID", "X-LaunchDarkly-Wrapper", "X-LaunchDarkly-Tags"}), + handlers.MaxAge(300), + ) +} var EventsCorsHeaders = handlers.CORS( handlers.AllowedOrigins([]string{"*"}), diff --git a/internal/dev_server/sdk/cors_test.go b/internal/dev_server/sdk/cors_test.go new file mode 100644 index 00000000..1d1ced9f --- /dev/null +++ b/internal/dev_server/sdk/cors_test.go @@ -0,0 +1,74 @@ +package sdk + +import ( + "net/http" + "net/http/httptest" + "testing" + + "github.com/gorilla/mux" + "github.com/stretchr/testify/assert" +) + +func newCorsTestServer(t *testing.T, methods ...string) *httptest.Server { + t.Helper() + router := mux.NewRouter() + router.Use(CorsHeadersForMethods(methods...)) + router.Methods(append(methods, http.MethodOptions)...). + PathPrefix("/test"). + HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusOK) + }) + return httptest.NewServer(router) +} + +// TestCorsHeaders verifies that the CORS middleware correctly supports the configured methods. +func TestCorsHeaders(t *testing.T) { + var allowedMethods = []string{http.MethodGet, "REPORT"} + server := newCorsTestServer(t, allowedMethods...) + defer server.Close() + + const origin = "http://example.com" + + tests := []struct { + name string + requestMethod string + wantAllowed bool + }{ + { + name: "GET is allowed", + requestMethod: http.MethodGet, + wantAllowed: true, + }, + { + name: "REPORT is allowed", + requestMethod: "REPORT", + wantAllowed: true, + }, + { + name: "PUT is not allowed", + requestMethod: http.MethodPut, + wantAllowed: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + req, err := http.NewRequest(http.MethodOptions, server.URL+"/test", nil) + assert.NoError(t, err) + req.Header.Set("Origin", origin) + req.Header.Set("Access-Control-Request-Method", tt.requestMethod) + + resp, err := http.DefaultClient.Do(req) + assert.NoError(t, err) + defer resp.Body.Close() + + if tt.wantAllowed { + assert.Equal(t, http.StatusOK, resp.StatusCode) + assert.Equal(t, "*", resp.Header.Get("Access-Control-Allow-Origin")) + } else { + assert.Equal(t, http.StatusMethodNotAllowed, resp.StatusCode) + assert.Empty(t, resp.Header.Get("Access-Control-Allow-Origin")) + } + }) + } +} diff --git a/internal/dev_server/sdk/routes.go b/internal/dev_server/sdk/routes.go index f651ad09..4a64c764 100644 --- a/internal/dev_server/sdk/routes.go +++ b/internal/dev_server/sdk/routes.go @@ -33,19 +33,22 @@ func BindRoutes(router *mux.Router) { router.PathPrefix("/msdk/evalx").Handler(GetProjectKeyFromAuthorizationHeader(http.HandlerFunc(GetClientFlags))) evalRouter := router.PathPrefix("/eval").Subrouter() - evalRouter.Use(CorsHeaders) + evalRouterMethods := []string{http.MethodGet, "REPORT", http.MethodOptions} + evalRouter.Use(CorsHeadersForMethods(evalRouterMethods...)) evalRouter.Use(GetProjectKeyFromEnvIdParameter("envId")) evalRouter.PathPrefix("/{envId}"). - Methods(http.MethodGet, "REPORT", http.MethodOptions). + Methods(evalRouterMethods...). HandlerFunc(StreamClientFlags) goalsRouter := router.Path("/sdk/goals/{envId}").Subrouter() - goalsRouter.Use(CorsHeaders) + goalsRouterMethods := []string{http.MethodGet, http.MethodOptions} + goalsRouter.Use(CorsHeadersForMethods(goalsRouterMethods...)) goalsRouter.Use(GetProjectKeyFromEnvIdParameter("envId")) - goalsRouter.Methods(http.MethodGet, http.MethodOptions).HandlerFunc(ConstantResponseHandler(http.StatusOK, "[]")) + goalsRouter.Methods(goalsRouterMethods...).HandlerFunc(ConstantResponseHandler(http.StatusOK, "[]")) evalXRouter := router.PathPrefix("/sdk/evalx/{envId}").Subrouter() - evalXRouter.Use(CorsHeaders) + evalXRouterMethods := []string{http.MethodGet, "REPORT", http.MethodOptions} + evalXRouter.Use(CorsHeadersForMethods(evalXRouterMethods...)) evalXRouter.Use(GetProjectKeyFromEnvIdParameter("envId")) - evalXRouter.Methods(http.MethodGet, http.MethodOptions, "REPORT").HandlerFunc(GetClientFlags) + evalXRouter.Methods(evalXRouterMethods...).HandlerFunc(GetClientFlags) } From 982b4fe02ae7a3558fd65845fd61d02f5194a689 Mon Sep 17 00:00:00 2001 From: vissersg Date: Tue, 24 Mar 2026 14:00:20 -0400 Subject: [PATCH 2/2] omit OPTIONS in CORS setup --- internal/dev_server/sdk/routes.go | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/internal/dev_server/sdk/routes.go b/internal/dev_server/sdk/routes.go index 4a64c764..569585cf 100644 --- a/internal/dev_server/sdk/routes.go +++ b/internal/dev_server/sdk/routes.go @@ -33,22 +33,22 @@ func BindRoutes(router *mux.Router) { router.PathPrefix("/msdk/evalx").Handler(GetProjectKeyFromAuthorizationHeader(http.HandlerFunc(GetClientFlags))) evalRouter := router.PathPrefix("/eval").Subrouter() - evalRouterMethods := []string{http.MethodGet, "REPORT", http.MethodOptions} + evalRouterMethods := []string{http.MethodGet, "REPORT"} evalRouter.Use(CorsHeadersForMethods(evalRouterMethods...)) evalRouter.Use(GetProjectKeyFromEnvIdParameter("envId")) evalRouter.PathPrefix("/{envId}"). - Methods(evalRouterMethods...). + Methods(append(evalRouterMethods, http.MethodOptions)...). HandlerFunc(StreamClientFlags) goalsRouter := router.Path("/sdk/goals/{envId}").Subrouter() - goalsRouterMethods := []string{http.MethodGet, http.MethodOptions} + goalsRouterMethods := []string{http.MethodGet} goalsRouter.Use(CorsHeadersForMethods(goalsRouterMethods...)) goalsRouter.Use(GetProjectKeyFromEnvIdParameter("envId")) - goalsRouter.Methods(goalsRouterMethods...).HandlerFunc(ConstantResponseHandler(http.StatusOK, "[]")) + goalsRouter.Methods(append(goalsRouterMethods, http.MethodOptions)...).HandlerFunc(ConstantResponseHandler(http.StatusOK, "[]")) evalXRouter := router.PathPrefix("/sdk/evalx/{envId}").Subrouter() - evalXRouterMethods := []string{http.MethodGet, "REPORT", http.MethodOptions} + evalXRouterMethods := []string{http.MethodGet, "REPORT"} evalXRouter.Use(CorsHeadersForMethods(evalXRouterMethods...)) evalXRouter.Use(GetProjectKeyFromEnvIdParameter("envId")) - evalXRouter.Methods(evalXRouterMethods...).HandlerFunc(GetClientFlags) + evalXRouter.Methods(append(evalXRouterMethods, http.MethodOptions)...).HandlerFunc(GetClientFlags) }