package middleware import ( "net/http" "strconv" "slices" "strings" "github.com/gin-gonic/gin" "github.com/tdvorak/seen/backend/internal/config" ) func CORS(cfg config.CORSConfig) gin.HandlerFunc { allowedOrigins := normalizeHeaderValues(cfg.AllowedOrigins) allowedMethods := normalizeHeaderValues(cfg.AllowedMethods) allowedHeaders := normalizeHeaderValues(cfg.AllowedHeaders) exposedHeaders := normalizeHeaderValues(cfg.ExposedHeaders) allowAnyOrigin := slices.Contains(allowedOrigins, "*") allowedMethodsValue := strings.Join(allowedMethods, ", ") allowedHeadersValue := strings.Join(allowedHeaders, ", ") exposedHeadersValue := strings.Join(exposedHeaders, ", ") return func(c *gin.Context) { origin := strings.TrimSpace(c.GetHeader("Origin")) if origin == "" { c.Next() return } if !allowAnyOrigin && !slices.Contains(allowedOrigins, origin) { if c.Request.Method == http.MethodOptions { c.AbortWithStatus(http.StatusForbidden) return } c.Next() return } if allowAnyOrigin && !cfg.AllowCredentials { c.Header("Access-Control-Allow-Origin", "*") } else { c.Header("Access-Control-Allow-Origin", origin) c.Header("Vary", "Origin") } c.Header("Access-Control-Allow-Methods", allowedMethodsValue) c.Header("Access-Control-Allow-Headers", allowedHeadersValue) if exposedHeadersValue != "" { c.Header("Access-Control-Expose-Headers", exposedHeadersValue) } if cfg.AllowCredentials { c.Header("Access-Control-Allow-Credentials", "true") } if cfg.MaxAge > 0 { c.Header("Access-Control-Max-Age", formatSeconds(cfg.MaxAge.Seconds())) } if c.Request.Method == http.MethodOptions { c.AbortWithStatus(http.StatusNoContent) return } c.Next() } } func normalizeHeaderValues(values []string) []string { items := make([]string, 0, len(values)) for _, value := range values { trimmed := strings.TrimSpace(value) if trimmed == "" { continue } items = append(items, trimmed) } return items } func formatSeconds(value float64) string { return strconv.Itoa(int(value)) }