package restapi import ( "net/http" "github.com/gin-gonic/gin" ) // withCORS returns a gin middleware that handles browser CORS preflight and // attaches Access-Control-Allow-* response headers when the request's Origin // is on the configured allow-list. Origins are compared exactly: scheme, // host, and port must match. An empty allow-list disables the middleware — // requests pass through untouched. Requests without an Origin header always // pass through, the middleware only acts when a browser actually asks. // // The middleware mounts before the anti-abuse layer so OPTIONS preflights // do not count against the rate-limit buckets for the eventual real call. func withCORS(allowedOrigins []string) gin.HandlerFunc { allowed := make(map[string]struct{}, len(allowedOrigins)) for _, origin := range allowedOrigins { allowed[origin] = struct{}{} } if len(allowed) == 0 { return func(c *gin.Context) { c.Next() } } return func(c *gin.Context) { origin := c.GetHeader("Origin") if origin == "" { c.Next() return } if _, ok := allowed[origin]; !ok { c.Next() return } c.Header("Access-Control-Allow-Origin", origin) c.Header("Vary", "Origin") c.Header("Access-Control-Allow-Credentials", "true") if c.Request.Method == http.MethodOptions { c.Header("Access-Control-Allow-Methods", "GET, POST, PUT, PATCH, DELETE, OPTIONS") if reqHeaders := c.GetHeader("Access-Control-Request-Headers"); reqHeaders != "" { c.Header("Access-Control-Allow-Headers", reqHeaders) } else { c.Header("Access-Control-Allow-Headers", "Content-Type, Authorization") } c.Header("Access-Control-Max-Age", "3600") c.AbortWithStatus(http.StatusNoContent) return } c.Next() } }