diff --git a/server.go b/server.go index 81be160..329cbb8 100644 --- a/server.go +++ b/server.go @@ -246,12 +246,15 @@ func handleListLeases(w http.ResponseWriter, r *http.Request) { } func handlePinLease(w http.ResponseWriter, r *http.Request) { - var req PinRequest - if err := json.NewDecoder(r.Body).Decode(&req); err != nil { - writeJSON(w, http.StatusBadRequest, ErrorResponse{Error: "invalid JSON: " + err.Error()}) + if err := r.ParseForm(); err != nil { + writeJSON(w, http.StatusBadRequest, ErrorResponse{Error: "invalid form data: " + err.Error()}) return } - defer r.Body.Close() + req := PinRequest{ + MAC: r.FormValue("mac"), + IP: r.FormValue("ip"), + Hostname: r.FormValue("hostname"), + } if req.MAC == "" || req.IP == "" { writeJSON(w, http.StatusBadRequest, ErrorResponse{Error: "mac and ip are required"}) @@ -280,12 +283,15 @@ func handlePinLease(w http.ResponseWriter, r *http.Request) { } func handleUnpinLease(w http.ResponseWriter, r *http.Request) { - var req PinRequest - if err := json.NewDecoder(r.Body).Decode(&req); err != nil { - writeJSON(w, http.StatusBadRequest, ErrorResponse{Error: "invalid JSON: " + err.Error()}) + if err := r.ParseForm(); err != nil { + writeJSON(w, http.StatusBadRequest, ErrorResponse{Error: "invalid form data: " + err.Error()}) return } - defer r.Body.Close() + req := PinRequest{ + MAC: r.FormValue("mac"), + IP: r.FormValue("ip"), + Hostname: r.FormValue("hostname"), + } if req.MAC == "" { writeJSON(w, http.StatusBadRequest, ErrorResponse{Error: "mac is required"}) diff --git a/web/app.js b/web/app.js index 3b263e9..35c09b2 100644 --- a/web/app.js +++ b/web/app.js @@ -26,20 +26,24 @@ async function apiFetch(path, opts) { var method = (opts.method || 'GET').toUpperCase(); var isPost = (method === 'POST' || method === 'PUT' || method === 'PATCH'); + // Attach CSRF token as query param (Zoraxy may validate it there) if (csrfToken && csrfToken !== '{{.csrfToken}}') { + var sep = path.indexOf('?') >= 0 ? '&' : '?'; + path = path + sep + 'csrfToken=' + encodeURIComponent(csrfToken); opts.headers['X-Zoraxy-Csrf'] = csrfToken; - // For POST requests, include token in body (Zoraxy validates via form field) - if (isPost) { - if (opts.body && typeof opts.body === 'object') { - opts.body.csrfToken = csrfToken; - } else { - opts.body = { csrfToken: csrfToken }; + } + + if (isPost && opts.body && typeof opts.body === 'object') { + // Send as URL-encoded form data (Zoraxy's CSRF middleware parses forms, not JSON) + var formBody = new URLSearchParams(); + for (var key in opts.body) { + if (opts.body.hasOwnProperty(key)) { + formBody.append(key, opts.body[key]); } } - } - if (opts.body && typeof opts.body === 'object') { - opts.body = JSON.stringify(opts.body); - opts.headers['Content-Type'] = 'application/json'; + formBody.append('csrfToken', csrfToken || ''); + opts.body = formBody.toString(); + opts.headers['Content-Type'] = 'application/x-www-form-urlencoded'; } var res = await fetch(API_BASE + path, opts); var text = await res.text();