PayButton: Fix CSP problems in Firefox (#4376)

* PayButton: Fix CSP problems in Firefox

Firefox does not support [`unsafe-hashes`](https://caniuse.com/?search=unsafe-hashes), so I figured it might be best to get rid of the inline event handlers in general.

Closes #4325.

* Account for multiple paybuttons on one page
This commit is contained in:
d11n 2022-12-12 12:27:26 +01:00 committed by GitHub
parent 5b20be8cfd
commit 484cf9d8a2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 37 additions and 16 deletions

View File

@ -1,14 +1,7 @@
@inject Security.ContentSecurityPolicies csp
@using BTCPayServer.Views.Stores @using BTCPayServer.Views.Stores
@model BTCPayServer.Plugins.PayButton.Models.PayButtonViewModel @model BTCPayServer.Plugins.PayButton.Models.PayButtonViewModel
@{ @{
ViewData.SetActivePage(StoreNavPages.PayButton, "Pay Button", Context.GetStoreData().Id); ViewData.SetActivePage(StoreNavPages.PayButton, "Pay Button", Context.GetStoreData().Id);
csp.AllowUnsafeHashes("onBTCPayFormSubmit(event);return false");
csp.AllowUnsafeHashes("handleSliderChange(event);return false");
csp.AllowUnsafeHashes("handleSliderInput(event);return false");
csp.AllowUnsafeHashes("handlePriceSlider(event);return false");
csp.AllowUnsafeHashes("handlePriceInput(event);return false");
csp.AllowUnsafeHashes("handlePlusMinus(event);return false");
} }
@section PageHeadContent { @section PageHeadContent {
@ -27,7 +20,7 @@
script.src = @(Safe.Json(Model.UrlRoot + "modal/btcpay.js")); script.src = @(Safe.Json(Model.UrlRoot + "modal/btcpay.js"));
document.getElementsByTagName('head')[0].append(script); document.getElementsByTagName('head')[0].append(script);
} }
function onBTCPayFormSubmit(event) { function handleFormSubmit(event) {
event.preventDefault(); event.preventDefault();
var xhttp = new XMLHttpRequest(); var xhttp = new XMLHttpRequest();
xhttp.onreadystatechange = function() { xhttp.onreadystatechange = function() {
@ -38,6 +31,12 @@
xhttp.open('POST', event.target.getAttribute('action'), true); xhttp.open('POST', event.target.getAttribute('action'), true);
xhttp.send(new FormData(event.target)); xhttp.send(new FormData(event.target));
} }
document.querySelectorAll(".btcpay-form").forEach(function(el) {
if (!el.dataset.initialized) {
el.addEventListener('submit', handleFormSubmit);
el.dataset.initialized = true;
}
});
</template> </template>
<template id="template-price-buttons" csp-allow> <template id="template-price-buttons" csp-allow>
function handlePlusMinus(event) { function handlePlusMinus(event) {
@ -55,6 +54,12 @@
el.value = price + step > max ? max : price + step; el.value = price + step > max ? max : price + step;
} }
} }
document.querySelectorAll(".btcpay-form .plus-minus").forEach(function(el) {
if (!el.dataset.initialized) {
el.addEventListener('click', handlePlusMinus);
el.dataset.initialized = true;
}
});
</template> </template>
<template id="template-price-input" csp-allow> <template id="template-price-input" csp-allow>
function handlePriceInput(event) { function handlePriceInput(event) {
@ -70,6 +75,12 @@
event.target.value = max; event.target.value = max;
} }
} }
document.querySelectorAll(".btcpay-form .btcpay-input-price").forEach(function(el) {
if (!el.dataset.initialized) {
el.addEventListener('input', handlePriceInput);
el.dataset.initialized = true;
}
});
</template> </template>
<template id="template-price-slider" csp-allow> <template id="template-price-slider" csp-allow>
function handleSliderChange(event) { function handleSliderChange(event) {
@ -86,13 +97,23 @@
} }
root.querySelector('.btcpay-input-range').value = el.value; root.querySelector('.btcpay-input-range').value = el.value;
} }
function handleSliderInput(event) { function handleSliderInput(event) {
event.target.closest('.btcpay-form').querySelector('.btcpay-input-price').value = event.target.value; event.target.closest('.btcpay-form').querySelector('.btcpay-input-price').value = event.target.value;
} }
document.querySelectorAll(".btcpay-form .btcpay-input-range").forEach(function(el) {
if (!el.dataset.initialized) {
el.addEventListener('input', handleSliderInput);
el.dataset.initialized = true;
}
});
document.querySelectorAll(".btcpay-form .btcpay-input-price").forEach(function(el) {
if (!el.dataset.initialized) {
el.addEventListener('change', handleSliderChange);
el.dataset.initialized = true;
}
});
</template> </template>
<script> <script>
const srvModel = @Safe.Json(Model); const srvModel = @Safe.Json(Model);
const payButtonCtrl = new Vue({ const payButtonCtrl = new Vue({

View File

@ -107,7 +107,7 @@ function inputChanges(event, buttonSize) {
// Styles // Styles
getStyles('template-paybutton-styles') + (srvModel.buttonType == '2' ? getStyles('template-slider-styles') : '') + getStyles('template-paybutton-styles') + (srvModel.buttonType == '2' ? getStyles('template-slider-styles') : '') +
// Form // Form
'<form method="POST"' + (srvModel.useModal ? ' onsubmit="onBTCPayFormSubmit(event);return false"' : '') + ' action="' + esc(srvModel.urlRoot) + actionUrl + '" class="btcpay-form btcpay-form--' + (srvModel.fitButtonInline ? 'inline' : 'block') +'">\n' + '<form method="POST" action="' + esc(srvModel.urlRoot) + actionUrl + '" class="btcpay-form btcpay-form--' + (srvModel.fitButtonInline ? 'inline' : 'block') +'">\n' +
addInput("storeId", srvModel.storeId); addInput("storeId", srvModel.storeId);
if (app) { if (app) {
@ -147,7 +147,7 @@ function inputChanges(event, buttonSize) {
const max = srvModel.max == null ? null : parseInt(srvModel.max); const max = srvModel.max == null ? null : parseInt(srvModel.max);
html += ' <div class="btcpay-custom-container">\n'; html += ' <div class="btcpay-custom-container">\n';
html += addInputPrice(priceInputName, srvModel.price, width, min, max, step, 'handleSliderChange(event);return false'); html += addInputPrice(priceInputName, srvModel.price, width, min, max, step);
if (allowCurrencySelection) html += addSelectCurrency(srvModel.currency); if (allowCurrencySelection) html += addSelectCurrency(srvModel.currency);
html += addSlider(srvModel.price, srvModel.min, srvModel.max, srvModel.step, width); html += addSlider(srvModel.price, srvModel.min, srvModel.max, srvModel.step, width);
html += ' </div>\n'; html += ' </div>\n';
@ -206,17 +206,17 @@ function addPlusMinusButton(type, step, min, max) {
min = min == null ? 1 : parseInt(min); min = min == null ? 1 : parseInt(min);
max = max == null ? null : parseInt(max); max = max == null ? null : parseInt(max);
return ` <button class="plus-minus" type="button" onclick="handlePlusMinus(event);return false" data-type="${type}" data-step="${step}" data-min="${min}" data-max="${max}">${type}</button>\n`; return ` <button class="plus-minus" type="button" data-type="${type}" data-step="${step}" data-min="${min}" data-max="${max}">${type}</button>\n`;
} }
function addInputPrice(name, price, widthInput, min = 0, max = 'none', step = 'any', onChange = null) { function addInputPrice(name, price, widthInput, min = 0, max = 'none', step = 'any') {
if (!price) price = min if (!price) price = min
return ` <input class="btcpay-input-price" type="number" name="${esc(name)}" min="${min}" max="${max}" step="${step}" value="${price}" data-price="${price}" style="width:${widthInput};" oninput="handlePriceInput(event);return false"${onChange ? ` onchange="${onChange}"` : ''} />\n`; return ` <input class="btcpay-input-price" type="number" name="${esc(name)}" min="${min}" max="${max}" step="${step}" value="${price}" data-price="${price}" style="width:${widthInput};" />\n`;
} }
function addSlider(price, min, max, step, width) { function addSlider(price, min, max, step, width) {
if (!price) price = min if (!price) price = min
return ` <input type="range" class="btcpay-input-range" min="${min}" max="${max}" step="${step}" value="${price}" style="width:${width};margin-bottom:15px;" oninput="handleSliderInput(event);return false" />\n`; return ` <input type="range" class="btcpay-input-range" min="${min}" max="${max}" step="${step}" value="${price}" style="width:${width};margin-bottom:15px;" />\n`;
} }
function addSelectCurrency(currency) { function addSelectCurrency(currency) {