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 // (exact match; an empty slice is a no-op). Returns 204 No Content for OPTIONS // preflight requests. // // It panics on "*": a wildcard matches no real Origin here, so passing it would // silently disable CORS. For allow-all use [CORSAllowAll] (development only). The // recommended wiring gates CORS by environment: // // var corsMW func(http.Handler) http.Handler // if strings.EqualFold(cfg.AppEnv, "local") { // corsMW = mw.CORSAllowAll() // dev: any origin // } else { // corsMW = mw.CORS(cfg.AllowedOrigins) // prod: explicit origins // } func CORS(origins []string) func(http.Handler) http.Handler { // "*" would be a silent no-op (exact-match only) — reject it loudly so a // misconfigured service fails to boot instead of quietly blocking browsers. 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 allows any origin by reflecting the request Origin (it does not set // Access-Control-Allow-Credentials). Development only — never in production. // // Use it for the local branch of the env-gated CORS convention; use [CORS] with // explicit origins everywhere else. Because [CORS] panics on "*", CORSAllowAll — not // a "*" in the origins list — is the way to allow all. 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) }) } }