thread-master/apps/backend/internal/middleware/cors.go

89 lines
2.5 KiB
Go
Raw Normal View History

2026-07-10 12:54:45 +00:00
package middleware
2026-07-13 08:59:13 +00:00
import (
"encoding/json"
"net/http"
"strings"
)
2026-07-10 12:54:45 +00:00
// CorsMiddleware — browser CORS for Harbor Desk FE.
// Note: cannot combine Access-Control-Allow-Origin: * with Allow-Credentials: true
// (browsers reject). FE uses Bearer token (no cookies), so no credentials header.
2026-07-13 08:59:13 +00:00
type CorsMiddleware struct {
allowedOrigins map[string]struct{}
}
2026-07-10 12:54:45 +00:00
2026-07-13 08:59:13 +00:00
func NewCorsMiddleware(origins []string) *CorsMiddleware {
allowed := make(map[string]struct{}, len(origins))
for _, origin := range origins {
origin = strings.TrimRight(strings.TrimSpace(origin), "/")
if origin != "" {
allowed[origin] = struct{}{}
}
}
return &CorsMiddleware{allowedOrigins: allowed}
2026-07-10 12:54:45 +00:00
}
func (m *CorsMiddleware) Handle(next http.HandlerFunc) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
2026-07-13 08:59:13 +00:00
if !m.setHeader(w, r) {
writeOriginDenied(w)
return
}
2026-07-10 12:54:45 +00:00
if r.Method == http.MethodOptions {
w.WriteHeader(http.StatusNoContent)
return
}
next(w, r)
}
}
func (m *CorsMiddleware) Handler() http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
2026-07-13 08:59:13 +00:00
if !m.setHeader(w, r) {
writeOriginDenied(w)
return
}
2026-07-10 12:54:45 +00:00
if r.Method == http.MethodOptions {
w.WriteHeader(http.StatusNoContent)
return
}
w.WriteHeader(http.StatusNotFound)
})
}
2026-07-13 08:59:13 +00:00
func writeOriginDenied(w http.ResponseWriter) {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusForbidden)
_ = json.NewEncoder(w).Encode(map[string]any{
"code": 403003, "message": "origin is not allowed", "data": nil, "error": nil,
})
}
func (m *CorsMiddleware) setHeader(w http.ResponseWriter, r *http.Request) bool {
origin := strings.TrimRight(strings.TrimSpace(r.Header.Get("Origin")), "/")
if origin != "" {
if _, ok := m.allowedOrigins[origin]; !ok {
return false
}
w.Header().Set("Access-Control-Allow-Origin", origin)
appendVary(w, "Origin")
2026-07-10 12:54:45 +00:00
}
w.Header().Set("Access-Control-Allow-Headers", "Content-Type, Authorization, X-Request-Id, device_id")
w.Header().Set("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS, PATCH")
2026-07-13 08:59:13 +00:00
w.Header().Set("Access-Control-Max-Age", "600")
2026-07-10 12:54:45 +00:00
w.Header().Set("Access-Control-Expose-Headers", "Content-Length, Content-Type")
2026-07-13 08:59:13 +00:00
return true
}
func appendVary(w http.ResponseWriter, value string) {
for _, existing := range w.Header().Values("Vary") {
for _, part := range strings.Split(existing, ",") {
if strings.EqualFold(strings.TrimSpace(part), value) {
return
}
}
}
w.Header().Add("Vary", value)
2026-07-10 12:54:45 +00:00
}