axe/internal/middleware/cors.go

67 lines
2.7 KiB
Go
Raw Normal View History

2026-10-18 19:07:25 +00:00
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()
}
}