package mw import "net/http" const ( allowedMethods = "GET, HEAD, PUT, PATCH, POST, DELETE, OPTIONS" allowedHeaders = "Content-Type, Authorization, X-Request-ID" ) // CORS sets cross-origin resource sharing headers for the provided origins. // Returns 204 No Content for OPTIONS preflight requests. // Pass the outermost origins first; an empty slice is a no-op. func CORS(origins []string) func(http.Handler) http.Handler { // "*" is a silent no-op here (exact-match only) — reject it loudly at // construction so a misconfigured service fails to boot instead of quietly // blocking every browser. For allow-all, call CORSAllowAll (development only). for _, o := range origins { if o == "*" { panic(`mw.CORS: "*" is not a valid origin — list explicit origins, or use CORSAllowAll() for allow-all`) } } originSet := make(map[string]struct{}, len(origins)) for _, o := range origins { 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") if origin != "" { if _, allowed := originSet[origin]; allowed { w.Header().Set("Access-Control-Allow-Origin", origin) w.Header().Set("Access-Control-Allow-Methods", allowedMethods) w.Header().Set("Access-Control-Allow-Headers", allowedHeaders) w.Header().Set("Access-Control-Allow-Credentials", "true") w.Header().Set("Vary", "Origin") } } if r.Method == http.MethodOptions { w.WriteHeader(http.StatusNoContent) return } next.ServeHTTP(w, r) }) } } // CORSAllowAll is a convenience wrapper that allows any origin. // Use only in development — never in production. func CORSAllowAll() func(http.Handler) http.Handler { return func(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { origin := r.Header.Get("Origin") if origin == "" { origin = "*" } w.Header().Set("Access-Control-Allow-Origin", origin) w.Header().Set("Access-Control-Allow-Methods", allowedMethods) w.Header().Set("Access-Control-Allow-Headers", allowedHeaders) w.Header().Set("Vary", "Origin") if r.Method == http.MethodOptions { w.WriteHeader(http.StatusNoContent) return } next.ServeHTTP(w, r) }) } }