OPTIONS /speak beantwortet Preflights mit 204 + Allow-Headers/Methods/Max-Age, wenn die Origin in server.allowed_origins steht; ungeliste Origins und leere Liste bekommen keine CORS-Header (Browser blockt). Nur /speak, kein anderer Endpunkt. /status ergänzt additiv cors:[origins] (immer vorhanden, [] wenn zu).
79 lines
2.9 KiB
Go
79 lines
2.9 KiB
Go
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)
|
|
}
|