|
1 | 1 | package middleware |
2 | 2 |
|
3 | 3 | import ( |
| 4 | + "net/http" |
| 5 | + "strings" |
| 6 | + |
4 | 7 | "github.com/gin-gonic/gin" |
5 | 8 | ) |
6 | 9 |
|
7 | | -func CORS() gin.HandlerFunc { |
| 10 | +// DevCORS returns a CORS middleware that only allows explicitly configured |
| 11 | +// origins. In production the SPA is served from the same origin as the API |
| 12 | +// (go:embed), so no CORS headers are needed at all. |
| 13 | +// |
| 14 | +// Developers running the Vite dev server on a different port can set |
| 15 | +// CORS_ALLOWED_ORIGINS=http://localhost:5173 to enable cross-origin |
| 16 | +// requests during local development. |
| 17 | +// |
| 18 | +// When allowedOrigins is empty the middleware is a no-op — no CORS |
| 19 | +// headers are emitted and browsers enforce same-origin policy. |
| 20 | +func DevCORS(allowedOrigins []string) gin.HandlerFunc { |
| 21 | + allowed := make(map[string]bool, len(allowedOrigins)) |
| 22 | + for _, o := range allowedOrigins { |
| 23 | + o = strings.TrimSpace(o) |
| 24 | + if o != "" { |
| 25 | + allowed[strings.TrimRight(o, "/")] = true |
| 26 | + } |
| 27 | + } |
| 28 | + |
8 | 29 | return func(c *gin.Context) { |
9 | | - c.Writer.Header().Set("Access-Control-Allow-Origin", c.Request.Header.Get("Origin")) |
| 30 | + // Fast path: no origins configured → no CORS headers (production) |
| 31 | + if len(allowed) == 0 { |
| 32 | + c.Next() |
| 33 | + return |
| 34 | + } |
10 | 35 |
|
| 36 | + origin := c.Request.Header.Get("Origin") |
| 37 | + if origin == "" || !allowed[origin] { |
| 38 | + c.Next() |
| 39 | + return |
| 40 | + } |
| 41 | + |
| 42 | + // Only set CORS headers for explicitly allowed origins |
| 43 | + c.Writer.Header().Set("Access-Control-Allow-Origin", origin) |
11 | 44 | c.Writer.Header().Set("Access-Control-Allow-Credentials", "true") |
12 | | - c.Writer.Header().Set("Access-Control-Allow-Headers", "Content-Type, Content-Length, Accept-Encoding, X-CSRF-Token, Authorization, accept, origin, Cache-Control, X-Requested-With, X-Cluster-Name") |
13 | | - c.Writer.Header().Set("Access-Control-Allow-Methods", "POST, OPTIONS, GET, PUT, DELETE") |
14 | | - if c.Request.Method == "OPTIONS" { |
15 | | - c.AbortWithStatus(204) |
| 45 | + c.Writer.Header().Set("Access-Control-Allow-Headers", |
| 46 | + "Content-Type, Authorization, X-Cluster-Name") |
| 47 | + c.Writer.Header().Set("Access-Control-Allow-Methods", |
| 48 | + "GET, POST, PUT, DELETE, PATCH, OPTIONS") |
| 49 | + c.Writer.Header().Set("Access-Control-Max-Age", "86400") |
| 50 | + // Vary so caches/CDNs don't serve a response with the wrong origin |
| 51 | + c.Writer.Header().Add("Vary", "Origin") |
| 52 | + |
| 53 | + if c.Request.Method == http.MethodOptions { |
| 54 | + c.AbortWithStatus(http.StatusNoContent) |
16 | 55 | return |
17 | 56 | } |
18 | 57 | c.Next() |
|
0 commit comments