package middleware import ( "net/http" "net/url" "strings" "github.com/noderunners/nip05api/internal/config" ) // CORS sends at most one Access-Control-Allow-Origin value (echo of request Origin). // Configure FRONTEND_URL, optional CORS_ORIGINS, and CORS_ALLOW_LOCALHOST / CORS_ALLOW_CREDENTIALS. func CORS(cfg *config.Config) 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 != "" && originAllowed(origin, cfg) { h := w.Header() h.Set("Access-Control-Allow-Origin", origin) h.Set("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS") h.Set("Access-Control-Allow-Headers", "Content-Type, X-API-Key, Authorization") h.Set("Access-Control-Max-Age", "86400") if cfg.CORSAllowCredentials { h.Set("Access-Control-Allow-Credentials", "true") } } if r.Method == http.MethodOptions { w.WriteHeader(http.StatusNoContent) return } next.ServeHTTP(w, r) }) } } func originAllowed(origin string, cfg *config.Config) bool { if origin == "" { return false } u, err := url.Parse(origin) if err != nil || u.Scheme == "" || u.Host == "" { return false } for _, allowed := range cfg.CORSExactOrigins() { if origin == allowed { return true } } if cfg.CORSAllowLocalhost && isLoopbackOrigin(u) { return true } return false } func isLoopbackOrigin(u *url.URL) bool { host := strings.TrimSuffix(strings.ToLower(u.Hostname()), ".") switch host { case "localhost", "127.0.0.1", "::1": return true default: return false } }