feat(api): CORS-Tür für POST /speak — OPTIONS-Preflight und ACAO aus Config
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).
This commit is contained in:
1 parent
cef6e6b9f2
commit
156c20dbb3
5 files changed
+174
-35
No files matched your search
@@ -0,0 +1,78 @@
|
||||
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)
|
||||
}
|
||||
Reference in new issue
Block a user