package api import ( "net/http" ) // CORS-Tür für POST /speak (Task 06) — und NUR für diesen Endpunkt: Ein // Browser (z. B. eine spätere LibreChat-Integration) darf genau die Ansage- // Schnittstelle cross-origin rufen, keine Steuer-Endpunkte. Die Erlaubnis // kommt ausschließlich aus der Config-Liste server.allowed_origins // (Env TALK_ALLOWED_ORIGINS): exakte Origins wie // "https://chat.az-gruppe.com"; der Eintrag "*" erlaubt jede Origin // ausdrücklich. Bewusster Default: LEER — dann sendet der Daemon gar keine // CORS-Header und der Browser blockt (Tür zu, bis sie konfiguriert wird). // // Ungelistete Origins bekommen bewusst trotzdem 204/202 ohne CORS-Header: // HTTP-seitig ist die Anfrage sauber, die Blockade macht der Browser (so // verrät der Daemon nichts über seine Erlaubnisliste). // CORS-Antwort-Header (Namen zentral, damit Preflight und echter POST // dieselben setzen). const ( headerAllowOrigin = "Access-Control-Allow-Origin" headerAllowMethods = "Access-Control-Allow-Methods" headerAllowHeaders = "Access-Control-Allow-Headers" headerMaxAge = "Access-Control-Max-Age" headerVary = "Vary" ) // preflightAge ist die Preflight-Cache-Dauer: 10 Minuten reichen für eine // Chat-Session und halten den OPTIONS-Chatter klein. const preflightAge = "600" // corsACAOLiefert den Wert für Access-Control-Allow-Origin: bei Wildcard- // Konfiguration das literale "*", sonst die exakt gematchte Origin (die // identisch mit dem Config-Eintrag ist). Leer heißt: nicht erlaubt. func (h *handler) corsACAO(r *http.Request) string { origin := r.Header.Get("Origin") if origin == "" { return "" } for _, allowed := range h.allowedOrigins { if allowed == "*" { return "*" } if allowed == origin { return allowed } } return "" } // setSpeakCors setzt die CORS-Header auf echten POST-/speak-Antworten, // wenn die Anfrage-Origin erlaubt ist. Vary: Origin steht immer (auch bei // geschlossener Tür), damit Caches die Antwort nie fälschlich origin- // übergreifend wiederverwenden. func (h *handler) setSpeakCors(w http.ResponseWriter, r *http.Request) { w.Header().Add(headerVary, "Origin") if acao := h.corsACAO(r); acao != "" { w.Header().Set(headerAllowOrigin, acao) } } // speakPreflight behandelt OPTIONS /speak (CORS-Preflight): 204 mit den // Freigabe-Headern bei gelisteter Origin, 204 ohne jegliche CORS-Header // sonst (ungelistet, fehlend oder Tür komplett zu) — der Browser verwirft // den eigentlichen Request, während der Daemon für Nicht-Browser-Clients // erreichbar bleibt. func (h *handler) speakPreflight(w http.ResponseWriter, r *http.Request) { w.Header().Add(headerVary, "Origin") if acao := h.corsACAO(r); acao != "" { w.Header().Set(headerAllowOrigin, acao) w.Header().Set(headerAllowMethods, "POST, OPTIONS") w.Header().Set(headerAllowHeaders, "Content-Type") w.Header().Set(headerMaxAge, preflightAge) } w.WriteHeader(http.StatusNoContent) }