67 lines
2.3 KiB
Go
67 lines
2.3 KiB
Go
package middleware
|
|
|
|
import (
|
|
"net/http"
|
|
"strings"
|
|
)
|
|
|
|
// CORS returns a middleware that adds Cross-Origin Resource Sharing headers to
|
|
// every response and handles OPTIONS preflight requests. It is designed for the
|
|
// Veylant dashboard (React SPA) which calls the proxy from a different origin.
|
|
//
|
|
// allowedOrigins is the list of permitted origins (e.g. ["http://localhost:3000",
|
|
// "https://dashboard.veylant.ai"]). An empty list disables CORS (no headers added).
|
|
// Pass ["*"] to allow any origin (development only — never use in production).
|
|
func CORS(allowedOrigins []string) func(http.Handler) http.Handler {
|
|
if len(allowedOrigins) == 0 {
|
|
// No origins configured — identity middleware.
|
|
return func(next http.Handler) http.Handler { return next }
|
|
}
|
|
|
|
originSet := make(map[string]struct{}, len(allowedOrigins))
|
|
wildcard := false
|
|
for _, o := range allowedOrigins {
|
|
if o == "*" {
|
|
wildcard = true
|
|
}
|
|
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")
|
|
|
|
// Only set CORS headers when the request carries an Origin header.
|
|
if origin != "" {
|
|
_, allowed := originSet[origin]
|
|
if wildcard || allowed {
|
|
w.Header().Set("Access-Control-Allow-Origin", origin)
|
|
w.Header().Set("Access-Control-Allow-Credentials", "true")
|
|
w.Header().Add("Vary", "Origin")
|
|
}
|
|
}
|
|
|
|
// Handle preflight (OPTIONS) — respond 204 and do not forward.
|
|
if r.Method == http.MethodOptions && origin != "" {
|
|
requestedMethod := r.Header.Get("Access-Control-Request-Method")
|
|
requestedHeaders := r.Header.Get("Access-Control-Request-Headers")
|
|
|
|
if requestedMethod != "" {
|
|
w.Header().Set("Access-Control-Allow-Methods", "GET, POST, PUT, PATCH, DELETE, OPTIONS")
|
|
if requestedHeaders != "" {
|
|
// Echo the requested headers back (normalised to title-case).
|
|
w.Header().Set("Access-Control-Allow-Headers", strings.ToTitle(requestedHeaders))
|
|
} else {
|
|
w.Header().Set("Access-Control-Allow-Headers", "Authorization, Content-Type, X-Request-Id")
|
|
}
|
|
w.Header().Set("Access-Control-Max-Age", "86400") // 24 h preflight cache
|
|
w.WriteHeader(http.StatusNoContent)
|
|
return
|
|
}
|
|
}
|
|
|
|
next.ServeHTTP(w, r)
|
|
})
|
|
}
|
|
}
|