package middleware import ( "strconv" "strings" "Noooste/garage-ui/internal/config" "github.com/gofiber/fiber/v3" ) // CORSMiddleware creates a CORS middleware from configuration func CORSMiddleware(cfg *config.CORSConfig) fiber.Handler { // If CORS is disabled, return a no-op middleware if !cfg.Enabled { return func(c fiber.Ctx) error { return c.Next() } } return func(c fiber.Ctx) error { origin := c.Get("Origin") // Check if origin is allowed. When credentials are allowed we refuse // to treat "*" as a match: reflecting an arbitrary Origin alongside // Access-Control-Allow-Credentials: true lets any site read responses // cross-origin with the user's session cookie. if origin != "" && isAllowedOrigin(origin, cfg.AllowedOrigins, cfg.AllowCredentials) { // Set CORS headers c.Set("Access-Control-Allow-Origin", origin) c.Set("Vary", "Origin") if cfg.AllowCredentials { c.Set("Access-Control-Allow-Credentials", "true") } // Set allowed methods if len(cfg.AllowedMethods) > 0 { c.Set("Access-Control-Allow-Methods", strings.Join(cfg.AllowedMethods, ", ")) } // Set allowed headers if len(cfg.AllowedHeaders) > 0 { c.Set("Access-Control-Allow-Headers", strings.Join(cfg.AllowedHeaders, ", ")) } // Set max age for preflight cache if cfg.MaxAge > 0 { c.Set("Access-Control-Max-Age", strconv.Itoa(cfg.MaxAge)) } } // Handle preflight requests if c.Method() == "OPTIONS" { return c.SendStatus(fiber.StatusNoContent) } return c.Next() } } // isAllowedOrigin checks if an origin is in the allowed list. // When allowCredentials is true, "*" is NOT honored — exact match is required. func isAllowedOrigin(origin string, allowedOrigins []string, allowCredentials bool) bool { for _, allowed := range allowedOrigins { if allowed == origin { return true } if allowed == "*" && !allowCredentials { return true } } return false }