package middleware import ( "net/http" "strings" "github.com/gin-gonic/gin" ) // ParseOrigins — разобрать CORS_ORIGINS: источники через запятую, // пустые куски и пробелы отбрасываются. Пустая строка — пустой список, // то есть ни один кросс-доменный запрос не получит заголовков. func ParseOrigins(raw string) []string { var out []string for _, part := range strings.Split(raw, ",") { if o := strings.TrimSpace(part); o != "" { out = append(out, o) } } return out } // CORS — заголовки для кросс-доменных запросов (микрофронт и разработка // с отдельного origin). // // Безопасный дефолт: список разрешённых источников приходит из // CORS_ORIGINS, и если он пуст — ничего не выдаём вовсе. Фронту на том же // домене CORS не нужен, а чужому домену его просто не должно быть. // // Разрешённый origin получает точный свой адрес в Allow-Origin // (не `*`: рядом Authorization, звёздочка запрещена спецификацией), // Vary: Origin — чтобы кэши не смешали ответы для разных источников. // // Preflight-OPTIONS отвечает 204 и обрывается ДО авторизации: браузер // спрашивает «можно ли» заранее, запроса с токеном ещё нет. func CORS(allowed []string) gin.HandlerFunc { allowedSet := make(map[string]bool, len(allowed)) for _, o := range allowed { allowedSet[o] = true } return func(c *gin.Context) { origin := c.GetHeader("Origin") if origin == "" || !allowedSet[origin] { // Без CORS-заголовков браузер сам закроет ответ — серверу // ничего не нужно делать. c.Next() return } h := c.Writer.Header() h.Set("Access-Control-Allow-Origin", origin) h.Add("Vary", "Origin") h.Set("Access-Control-Allow-Methods", "GET, POST, PATCH, DELETE, OPTIONS") h.Set("Access-Control-Allow-Headers", "Authorization, Content-Type, X-Request-Id") h.Set("Access-Control-Expose-Headers", "X-Request-Id") // Кэш preflight: браузер не будет переспрашивать каждую минуту. h.Set("Access-Control-Max-Age", "600") if c.Request.Method == http.MethodOptions { c.AbortWithStatus(http.StatusNoContent) return } c.Next() } }