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:
1 parent
82a36a35c7
commit
7837f90d00
2 files changed
+48
-1
No files matched your search
@@ -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
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user