package middleware import ( "encoding/json" "net/http" "strings" ) // 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. type CorsMiddleware struct { allowedOrigins map[string]struct{} } 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} } func (m *CorsMiddleware) Handle(next http.HandlerFunc) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { if !m.setHeader(w, r) { writeOriginDenied(w) return } 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) { if !m.setHeader(w, r) { writeOriginDenied(w) return } if r.Method == http.MethodOptions { w.WriteHeader(http.StatusNoContent) return } w.WriteHeader(http.StatusNotFound) }) } 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") } 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") w.Header().Set("Access-Control-Max-Age", "600") w.Header().Set("Access-Control-Expose-Headers", "Content-Length, Content-Type") 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) }