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.
33 lines
1.4 KiB
Go
33 lines
1.4 KiB
Go
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)
|
|
})
|
|
}
|