67 lines
2.7 KiB
Go
67 lines
2.7 KiB
Go
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()
|
||
}
|
||
}
|