fix: switch to form-encoded POST + CSRF as query param + form field

This commit is contained in:
Claus Lohmar 2026-07-24 10:54:45 +00:00
parent 78cae8dea4
commit 76e7fc3d5c
2 changed files with 28 additions and 18 deletions

View file

@ -246,12 +246,15 @@ func handleListLeases(w http.ResponseWriter, r *http.Request) {
} }
func handlePinLease(w http.ResponseWriter, r *http.Request) { func handlePinLease(w http.ResponseWriter, r *http.Request) {
var req PinRequest if err := r.ParseForm(); err != nil {
if err := json.NewDecoder(r.Body).Decode(&req); err != nil { writeJSON(w, http.StatusBadRequest, ErrorResponse{Error: "invalid form data: " + err.Error()})
writeJSON(w, http.StatusBadRequest, ErrorResponse{Error: "invalid JSON: " + err.Error()})
return return
} }
defer r.Body.Close() req := PinRequest{
MAC: r.FormValue("mac"),
IP: r.FormValue("ip"),
Hostname: r.FormValue("hostname"),
}
if req.MAC == "" || req.IP == "" { if req.MAC == "" || req.IP == "" {
writeJSON(w, http.StatusBadRequest, ErrorResponse{Error: "mac and ip are required"}) 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) { func handleUnpinLease(w http.ResponseWriter, r *http.Request) {
var req PinRequest if err := r.ParseForm(); err != nil {
if err := json.NewDecoder(r.Body).Decode(&req); err != nil { writeJSON(w, http.StatusBadRequest, ErrorResponse{Error: "invalid form data: " + err.Error()})
writeJSON(w, http.StatusBadRequest, ErrorResponse{Error: "invalid JSON: " + err.Error()})
return return
} }
defer r.Body.Close() req := PinRequest{
MAC: r.FormValue("mac"),
IP: r.FormValue("ip"),
Hostname: r.FormValue("hostname"),
}
if req.MAC == "" { if req.MAC == "" {
writeJSON(w, http.StatusBadRequest, ErrorResponse{Error: "mac is required"}) writeJSON(w, http.StatusBadRequest, ErrorResponse{Error: "mac is required"})

View file

@ -26,20 +26,24 @@ async function apiFetch(path, opts) {
var method = (opts.method || 'GET').toUpperCase(); var method = (opts.method || 'GET').toUpperCase();
var isPost = (method === 'POST' || method === 'PUT' || method === 'PATCH'); var isPost = (method === 'POST' || method === 'PUT' || method === 'PATCH');
// Attach CSRF token as query param (Zoraxy may validate it there)
if (csrfToken && csrfToken !== '{{.csrfToken}}') { if (csrfToken && csrfToken !== '{{.csrfToken}}') {
var sep = path.indexOf('?') >= 0 ? '&' : '?';
path = path + sep + 'csrfToken=' + encodeURIComponent(csrfToken);
opts.headers['X-Zoraxy-Csrf'] = 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') { if (isPost && opts.body && typeof opts.body === 'object') {
opts.body.csrfToken = csrfToken; // Send as URL-encoded form data (Zoraxy's CSRF middleware parses forms, not JSON)
} else { var formBody = new URLSearchParams();
opts.body = { csrfToken: csrfToken }; for (var key in opts.body) {
if (opts.body.hasOwnProperty(key)) {
formBody.append(key, opts.body[key]);
} }
} }
} formBody.append('csrfToken', csrfToken || '');
if (opts.body && typeof opts.body === 'object') { opts.body = formBody.toString();
opts.body = JSON.stringify(opts.body); opts.headers['Content-Type'] = 'application/x-www-form-urlencoded';
opts.headers['Content-Type'] = 'application/json';
} }
var res = await fetch(API_BASE + path, opts); var res = await fetch(API_BASE + path, opts);
var text = await res.text(); var text = await res.text();