From efc007cd937ebda2933632175200cd93e57548c4 Mon Sep 17 00:00:00 2001 From: rameel Date: Mon, 5 Oct 2026 02:52:46 +0500 Subject: [PATCH] config: preserve antiforgery tokens after boosted navigation read_antiforgery() always returns an object, so boosted responses without metadata overwrite the saved token with empty values. Later POST requests that rely on automatic token injection can then fail antiforgery validation. Only update the saved configuration when the response contains a non-empty request token. --- src/Ramstack.HtmxToolkit/wwwroot/htmx-toolkit.js | 2 +- src/Ramstack.HtmxToolkit/wwwroot/htmx-toolkit.min.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Ramstack.HtmxToolkit/wwwroot/htmx-toolkit.js b/src/Ramstack.HtmxToolkit/wwwroot/htmx-toolkit.js index 767245a..fb95764 100644 --- a/src/Ramstack.HtmxToolkit/wwwroot/htmx-toolkit.js +++ b/src/Ramstack.HtmxToolkit/wwwroot/htmx-toolkit.js @@ -99,7 +99,7 @@ document._r_htmx ||= ((document, htmx) => { const update_antiforgery = content => { let doc = new DOMParser().parseFromString(content || "", "text/html"); let val = read_antiforgery(doc); - val && (antiforgery = val); + val.requestToken && (antiforgery = val); }; listen("htmx:afterOnLoad", e => { diff --git a/src/Ramstack.HtmxToolkit/wwwroot/htmx-toolkit.min.js b/src/Ramstack.HtmxToolkit/wwwroot/htmx-toolkit.min.js index 3125b6b..f065553 100644 --- a/src/Ramstack.HtmxToolkit/wwwroot/htmx-toolkit.min.js +++ b/src/Ramstack.HtmxToolkit/wwwroot/htmx-toolkit.min.js @@ -1 +1 @@ -document._r_htmx||=((e,t)=>{const r=e=>console.warn(`ramstack.htmxtoolkit: ${e}`);t.defineExtension&&t.defineExtension("ramstack-morph",{isInlineSwap:e=>"outerMorph"===e||"outerSync"===e,handleSwap(e,t,n){if("textContent"===e)return t.textContent=n.textContent,[t];let o="innerMorph"===e?"innerHTML":"outerMorph"===e?"outerHTML":null;if(o){let a=globalThis.Idiomorph;if(a?.morph)return a.morph(t,n.children,{morphStyle:o});r(`Idiomorph is unavailable; falling back from ${e} to ${o}`);let i=[...n.childNodes];return"innerHTML"===o?t.replaceChildren(...i):t.replaceWith(...i),i}if("outerSync"===e){r("outerSync requires HTMX 4.x; falling back to attribute sync and innerHTML");let e=n.firstElementChild;if(e){for(let r of[...t.attributes])e.hasAttribute(r.name)||t.removeAttribute(r.name);for(let r of e.attributes)t.setAttribute(r.name,r.value);let r=e.childNodes;return t.replaceChildren(...r),r}}}});const n=(t,r)=>{e.addEventListener(t,r)},o=e=>{let t=e.querySelector("meta[name='htmx-config']")?.dataset||{};return{headerName:t.antiforgeryHeaderName,formFieldName:t.antiforgeryFormFieldName,requestToken:t.antiforgeryRequestToken}};let a=o(e);const i=(e,t,r)=>{if(!/^get$/i.test(e)){const{headerName:e,formFieldName:n,requestToken:o}=a;o&&(r.has?.(n)||r[n]||(e?t[e]=o:r.set?r.set(n,o):r[n]=o))}},l=e=>{let t=(new DOMParser).parseFromString(e||"","text/html"),r=o(t);r&&(a=r)};return n("htmx:afterOnLoad",e=>{let t=e.detail;t.boosted&&l(t.xhr.responseText)}),n("htmx:after:request",e=>{let t=e.detail.ctx;t.boosted&&l(t.text)}),n("htmx:configRequest",e=>{let t=e.detail;i(t.verb,t.headers,t.parameters)}),n("htmx:config:request",e=>{let t=e.detail.ctx.request;i(t.method,t.headers,t.body)}),n("rs:event",e=>{for(let r of e.detail.value||e.detail)t.trigger(e.target,r.key,r.value)}),!0})(document,htmx); \ No newline at end of file +document._r_htmx||=((e,t)=>{const r=e=>console.warn(`ramstack.htmxtoolkit: ${e}`);t.defineExtension&&t.defineExtension("ramstack-morph",{isInlineSwap:e=>"outerMorph"===e||"outerSync"===e,handleSwap(e,t,n){if("textContent"===e)return t.textContent=n.textContent,[t];let o="innerMorph"===e?"innerHTML":"outerMorph"===e?"outerHTML":null;if(o){let a=globalThis.Idiomorph;if(a?.morph)return a.morph(t,n.children,{morphStyle:o});r(`Idiomorph is unavailable; falling back from ${e} to ${o}`);let i=[...n.childNodes];return"innerHTML"===o?t.replaceChildren(...i):t.replaceWith(...i),i}if("outerSync"===e){r("outerSync requires HTMX 4.x; falling back to attribute sync and innerHTML");let e=n.firstElementChild;if(e){for(let r of[...t.attributes])e.hasAttribute(r.name)||t.removeAttribute(r.name);for(let r of e.attributes)t.setAttribute(r.name,r.value);let r=e.childNodes;return t.replaceChildren(...r),r}}}});const n=(t,r)=>{e.addEventListener(t,r)},o=e=>{let t=e.querySelector("meta[name='htmx-config']")?.dataset||{};return{headerName:t.antiforgeryHeaderName,formFieldName:t.antiforgeryFormFieldName,requestToken:t.antiforgeryRequestToken}};let a=o(e);const i=(e,t,r)=>{if(!/^get$/i.test(e)){const{headerName:e,formFieldName:n,requestToken:o}=a;o&&(r.has?.(n)||r[n]||(e?t[e]=o:r.set?r.set(n,o):r[n]=o))}},l=e=>{let t=(new DOMParser).parseFromString(e||"","text/html"),r=o(t);r.requestToken&&(a=r)};return n("htmx:afterOnLoad",e=>{let t=e.detail;t.boosted&&l(t.xhr.responseText)}),n("htmx:after:request",e=>{let t=e.detail.ctx;t.boosted&&l(t.text)}),n("htmx:configRequest",e=>{let t=e.detail;i(t.verb,t.headers,t.parameters)}),n("htmx:config:request",e=>{let t=e.detail.ctx.request;i(t.method,t.headers,t.body)}),n("rs:event",e=>{for(let r of e.detail.value||e.detail)t.trigger(e.target,r.key,r.value)}),!0})(document,htmx); \ No newline at end of file