package httputil import ( "net/http" "slices" "strings" "kjol/appenv" ) // CorsConfig configures CorsMiddleware. AllowedDomains is consulted only outside // development (in development every origin is allowed). BundleVersion, when set, // is called to stamp the X-Bundle-Version header on /api/ responses; leave it nil // to skip that header. type CorsConfig struct { AllowedDomains []string BundleVersion func() string } // CorsMiddleware returns middleware that applies CORS headers to all requests. // It is a constructor (not the middleware itself) so the app can inject its // allowed domains and bundle-version source without this package importing app // config or handlers. func CorsMiddleware(cfg CorsConfig) 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 appenv.Environment != appenv.EnvTypeDevelopment { if len(cfg.AllowedDomains) > 0 && slices.Contains(cfg.AllowedDomains, origin) { w.Header().Set("Access-Control-Allow-Origin", origin) } } else { w.Header().Set("Access-Control-Allow-Origin", "*") } w.Header().Set("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS") w.Header().Set("Access-Control-Allow-Headers", "Content-Type, Authorization") w.Header().Set("Access-Control-Expose-Headers", "X-Bundle-Version") w.Header().Set("Access-Control-Allow-Credentials", "true") if cfg.BundleVersion != nil { if version := cfg.BundleVersion(); version != "" && strings.HasPrefix(r.URL.Path, "/api/") { w.Header().Set("X-Bundle-Version", version) } } // Handle preflight OPTIONS request if r.Method == http.MethodOptions { w.WriteHeader(http.StatusNoContent) return } next.ServeHTTP(w, r) }) } }