gateway: a listed web page's call with a gateway key is answered by the running gateway, not refused 401

On the real server lanGuard sits in front of Handler and, for a gateway
key sent from this computer, records the caller and puts magpie's own
token in the key's place, so corsGuard never saw the key and refused every
listed page's call with "a web page calls magpie with a gateway key".
corsGuard now also takes the caller lanGuard recorded.

Verified: TestCORSKeyThroughTheServer (lanGuard(Handler), a listed origin
with and without a key) fails without the change (401 for the keyed call)
and passes with it; TestCORSOrigins passes.
This commit is contained in:
yetone committed 2026-10-07 04:31:03 +08:00
1 parent 82a36a35c7
commit 7837f90d00
2 files changed
+48 -1

No files matched your search

+5 -1
View File
@@ -42,7 +42,11 @@ func corsGuard(next http.Handler) http.Handler {
w.WriteHeader(http.StatusNoContent)
return
}
if !slices.ContainsFunc(callerKeys(r), func(k string) bool { _, ok := access.Authenticate(k); return ok }) {
// lanGuard, in front of Handler on the real server, has already
// taken the key and put magpie's own token in its place
keyed := access.Caller(r.Context()).KeyID != "" ||
slices.ContainsFunc(callerKeys(r), func(k string) bool { _, ok := access.Authenticate(k); return ok })
if !keyed {
writeError(w, provider.Chat, http.StatusUnauthorized, "a web page calls magpie with a gateway key: create one in magpie's Gateway page and send it as Authorization: Bearer <key> (or x-api-key)")
return
}
+43
View File
@@ -117,3 +117,46 @@ func TestCORSOrigins(t *testing.T) {
t.Fatalf("agent: %d %v", w.Code, w.Header())
}
}
// On the real server lanGuard sits in front of Handler and, for a gateway
// key sent from this computer, records who called and puts magpie's own
// token in the key's place; a listed page's keyed call is still answered.
func TestCORSKeyThroughTheServer(t *testing.T) {
fresh(t)
up := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
io.WriteString(w, `{"choices":[{"message":{"role":"assistant","content":"ok"},"finish_reason":"stop"}],"usage":{"prompt_tokens":3,"completion_tokens":1}}`)
}))
defer up.Close()
if err := provider.Save(provider.Provider{ID: "plan", Name: "Plan", Key: "upstream-secret", Chat: up.URL + "/v1", Models: []string{"m1"}}); err != nil {
t.Fatal(err)
}
secret, err := access.Update("add-key", access.Change{Name: "Web app"})
if err != nil {
t.Fatal(err)
}
s := settings.Load()
s.CORSOrigins = []string{"http://localhost:3000"}
if err := settings.Save(s); err != nil {
t.Fatal(err)
}
srv := lanGuard(New().Handler())
for _, key := range []string{secret, ""} {
r := httptest.NewRequest("POST", "/v1/chat/completions", strings.NewReader(chatReq))
r.RemoteAddr = "127.0.0.1:50123"
r.Header.Set("Origin", "http://localhost:3000")
r.Header.Set("Content-Type", "application/json")
if key != "" {
r.Header.Set("Authorization", "Bearer "+key)
}
w := httptest.NewRecorder()
srv.ServeHTTP(w, r)
want := http.StatusOK
if key == "" {
want = http.StatusUnauthorized
}
if w.Code != want || w.Header().Get("Access-Control-Allow-Origin") != "http://localhost:3000" {
t.Errorf("key %q: %d %v %s", key, w.Code, w.Header(), w.Body)
}
}
}