84 lines
2.9 KiB
Go
84 lines
2.9 KiB
Go
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)
|
|
})
|
|
}
|
|
}
|