fix(node): proper CORS middleware + preflight handling
The desktop Electron renderer runs at http://127.0.0.1:5173 (dev) or file:// (prod); the node HTTP API is at a different origin by design. Browsers enforce CORS, and our per-handler `Access-Control-Allow-Origin: *` header only covered the happy path — preflight OPTIONS requests, which browsers send before any POST with a JSON body or Authorization header, fell through to the 404 handler without CORS headers and the subsequent real request was blocked. Added node/cors.go — a single middleware that: * Sets Access-Control-Allow-Origin / -Methods / -Headers / -Expose-Headers / -Max-Age on every response. * Short-circuits OPTIONS with 204, never invoking the mux. Wired into stats.go:ListenAndServe so the wrapping is unconditional (the node's security model gates writes by token + Ed25519 signature, not by origin, so wide CORS is the correct default). Cleaned up the now-redundant per-jsonOK/jsonErr Allow-Origin setters in api_common.go — the middleware sets a single consistent header instead of two collisions from handlers that both write one. Symptom before: `net::ERR_FAILED` / "CORS policy blocked" errors in the Electron devtools console when hitting /api/* or /relay/*. Symptom after: clean GET/POST, preflight answers in ~1ms.
This commit is contained in:
@@ -15,13 +15,11 @@ import (
|
||||
|
||||
func jsonOK(w http.ResponseWriter, v any) {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.Header().Set("Access-Control-Allow-Origin", "*")
|
||||
_ = json.NewEncoder(w).Encode(v)
|
||||
}
|
||||
|
||||
func jsonErr(w http.ResponseWriter, err error, code int) {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.Header().Set("Access-Control-Allow-Origin", "*")
|
||||
w.WriteHeader(code)
|
||||
_ = json.NewEncoder(w).Encode(map[string]string{"error": err.Error()})
|
||||
}
|
||||
|
||||
32
node/cors.go
Normal file
32
node/cors.go
Normal file
@@ -0,0 +1,32 @@
|
||||
package node
|
||||
|
||||
import "net/http"
|
||||
|
||||
// withCORS wraps any http.Handler so every response carries the CORS
|
||||
// headers browser-based clients (Electron renderer, web explorer from a
|
||||
// different origin, mobile webview) need. Also short-circuits OPTIONS
|
||||
// preflight requests with a 204 — without this, POST /api/tx with a
|
||||
// JSON body triggers a preflight that the regular handler answers as
|
||||
// 404/405 and the browser refuses the follow-up.
|
||||
//
|
||||
// The allow-list is wide on purpose. The node's security model doesn't
|
||||
// rely on same-origin — API tokens (DCHAIN_API_TOKEN + DCHAIN_API_PRIVATE)
|
||||
// and Ed25519 tx signatures are what gate writes. Cross-origin access is
|
||||
// a first-class feature here, not an attack vector.
|
||||
func withCORS(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
h := w.Header()
|
||||
h.Set("Access-Control-Allow-Origin", "*")
|
||||
h.Set("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS, HEAD, PATCH")
|
||||
h.Set("Access-Control-Allow-Headers", "Authorization, Content-Type, X-Requested-With")
|
||||
h.Set("Access-Control-Expose-Headers", "Content-Length, Content-Type")
|
||||
h.Set("Access-Control-Max-Age", "86400") // cache preflight for a day
|
||||
|
||||
if r.Method == http.MethodOptions {
|
||||
// Preflight. Don't hand to the mux — just answer.
|
||||
w.WriteHeader(http.StatusNoContent)
|
||||
return
|
||||
}
|
||||
next.ServeHTTP(w, r)
|
||||
})
|
||||
}
|
||||
@@ -310,7 +310,10 @@ func (t *Tracker) ServeHTTP(q QueryFunc, fns ...func(*http.ServeMux)) http.Handl
|
||||
}
|
||||
|
||||
// ListenAndServe starts the HTTP stats server on addr (e.g. ":8080").
|
||||
// All responses pass through withCORS so browser + Electron clients
|
||||
// get correct Access-Control-* headers and preflight OPTIONS requests
|
||||
// are answered with 204 instead of falling through to the 404 handler.
|
||||
func (t *Tracker) ListenAndServe(addr string, q QueryFunc, fns ...func(*http.ServeMux)) error {
|
||||
handler := t.ServeHTTP(q, fns...)
|
||||
handler := withCORS(t.ServeHTTP(q, fns...))
|
||||
return http.ListenAndServe(addr, handler)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user