mirror of
https://github.com/aljazceru/btcpayserver.git
synced 2026-01-07 08:04:29 +01:00
Pay Button: Fix CSP violations for custom amount and slider (#3334)
Fixes #3241.
This commit is contained in:
@@ -17,6 +17,13 @@ function esc(input) {
|
||||
;
|
||||
}
|
||||
|
||||
function unesc(input) {
|
||||
return ('' + input)
|
||||
.replace(/&/g, '&')
|
||||
.replace(/</g, '<')
|
||||
.replace(/>/g, '>')
|
||||
}
|
||||
|
||||
Vue.use(VeeValidate);
|
||||
var dictionary = {
|
||||
en: {
|
||||
@@ -30,13 +37,28 @@ var dictionary = {
|
||||
VeeValidate.Validator.localize(dictionary);
|
||||
|
||||
function getStyles (styles) {
|
||||
return document.getElementById(styles).innerHTML.trim().replace(/\s{2}/g, '') + '\n'
|
||||
return document.getElementById(styles).innerHTML.replace(/\s{2}/g, '').trim() + '\n'
|
||||
}
|
||||
|
||||
function getScripts(srvModel) {
|
||||
if (!srvModel.useModal) return ''
|
||||
const template = document.getElementById('template-get-scripts')
|
||||
return template.innerHTML.replace(/&/g, '&')
|
||||
const scripts = []
|
||||
if (srvModel.useModal) {
|
||||
const modal = document.getElementById('template-modal')
|
||||
scripts.push(unesc(modal.innerHTML))
|
||||
}
|
||||
if (srvModel.buttonType === '1') {
|
||||
const priceButtons = document.getElementById('template-price-buttons')
|
||||
const priceInput = document.getElementById('template-price-input')
|
||||
scripts.push(unesc(priceButtons.innerHTML))
|
||||
scripts.push(unesc(priceInput.innerHTML))
|
||||
}
|
||||
if (srvModel.buttonType === '2') {
|
||||
const priceSlider = document.getElementById('template-price-slider')
|
||||
const priceInput = document.getElementById('template-price-input')
|
||||
scripts.push(unesc(priceSlider.innerHTML))
|
||||
scripts.push(unesc(priceInput.innerHTML))
|
||||
}
|
||||
return scripts
|
||||
}
|
||||
|
||||
function inputChanges(event, buttonSize) {
|
||||
@@ -44,41 +66,36 @@ function inputChanges(event, buttonSize) {
|
||||
srvModel.buttonSize = buttonSize;
|
||||
}
|
||||
|
||||
var isFixedAmount = srvModel.buttonType == 0
|
||||
var isCustomAmount = srvModel.buttonType == 1
|
||||
var isSlider = srvModel.buttonType == 2
|
||||
|
||||
var width = "209px";
|
||||
var height = "57px";
|
||||
var widthInput = "3em";
|
||||
let width = '209px';
|
||||
let height = '57px';
|
||||
let widthInput = '3em';
|
||||
if (srvModel.buttonSize === 0) {
|
||||
width = "146px";
|
||||
widthInput = "2em";
|
||||
height = "40px";
|
||||
width = '146px';
|
||||
widthInput = '2em';
|
||||
height = '40px';
|
||||
} else if (srvModel.buttonSize === 1) {
|
||||
width = "168px";
|
||||
height = "46px";
|
||||
width = '168px';
|
||||
height = '46px';
|
||||
} else if (srvModel.buttonSize === 2) {
|
||||
width = "209px";
|
||||
height = "57px";
|
||||
width = '209px';
|
||||
height = '57px';
|
||||
}
|
||||
var actionUrl = "api/v1/invoices";
|
||||
var priceInputName = "price";
|
||||
var app = srvModel.appIdEndpoint? srvModel.apps.find(value => value.id === srvModel.appIdEndpoint ): null;
|
||||
var allowCurrencySelection = true;
|
||||
let actionUrl = 'api/v1/invoices';
|
||||
let priceInputName = 'price';
|
||||
let app = srvModel.appIdEndpoint? srvModel.apps.find(value => value.id === srvModel.appIdEndpoint ): null;
|
||||
let allowCurrencySelection = true;
|
||||
if (app) {
|
||||
|
||||
if (app.appType.toLowerCase() == "pointofsale") {
|
||||
actionUrl = "apps/" + app.id + "/pos";
|
||||
} else if (app.appType.toLowerCase() == "crowdfund") {
|
||||
actionUrl = "apps/" + app.id + "/crowdfund";
|
||||
if (app.appType.toLowerCase() === 'pointofsale') {
|
||||
actionUrl = `apps/${app.id}/pos`;
|
||||
} else if (app.appType.toLowerCase() === 'crowdfund') {
|
||||
actionUrl = `apps/${app.id}/crowdfund`;
|
||||
} else {
|
||||
actionUrl = "api/v1/invoices";
|
||||
actionUrl = 'api/v1/invoices';
|
||||
app = null;
|
||||
}
|
||||
|
||||
if (actionUrl != "api/v1/invoices") {
|
||||
priceInputName = "amount";
|
||||
if (actionUrl !== 'api/v1/invoices') {
|
||||
priceInputName = 'amount';
|
||||
allowCurrencySelection = false;
|
||||
srvModel.useModal = false;
|
||||
}
|
||||
@@ -86,96 +103,77 @@ function inputChanges(event, buttonSize) {
|
||||
|
||||
var html =
|
||||
// Styles
|
||||
getStyles('template-paybutton-styles') + (isSlider ? getStyles('template-slider-styles') : '') +
|
||||
getStyles('template-paybutton-styles') + (srvModel.buttonType === '2' ? getStyles('template-slider-styles') : '') +
|
||||
// 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' +
|
||||
addInput("storeId", srvModel.storeId);
|
||||
|
||||
if(app){
|
||||
if (app) {
|
||||
if (srvModel.orderId) html += addInput("orderId", srvModel.orderId);
|
||||
if (srvModel.serverIpn) html += addInput("notificationUrl", srvModel.serverIpn);
|
||||
if (srvModel.browserRedirect) html += addInput("redirectUrl", srvModel.browserRedirect);
|
||||
if (srvModel.appChoiceKey) html += addInput("choiceKey", srvModel.appChoiceKey);
|
||||
|
||||
}else{
|
||||
} else {
|
||||
if (srvModel.useModal) html += addInput("jsonResponse", true);
|
||||
|
||||
if (srvModel.orderId) html += addInput("orderId", srvModel.orderId);
|
||||
if (srvModel.checkoutDesc) html += addInput("checkoutDesc", srvModel.checkoutDesc);
|
||||
|
||||
|
||||
if (srvModel.serverIpn) html += addInput("serverIpn", srvModel.serverIpn);
|
||||
|
||||
if (srvModel.browserRedirect) html += addInput("browserRedirect", srvModel.browserRedirect);
|
||||
|
||||
if (srvModel.notifyEmail) html += addInput("notifyEmail", srvModel.notifyEmail);
|
||||
|
||||
if (srvModel.checkoutQueryString) html += addInput("checkoutQueryString", srvModel.checkoutQueryString);
|
||||
}
|
||||
|
||||
// Fixed amount: Add price and currency as hidden inputs
|
||||
if (isFixedAmount) {
|
||||
if (srvModel.price)
|
||||
html += addInput(priceInputName, srvModel.price);
|
||||
if(allowCurrencySelection){
|
||||
html += addInput("currency", srvModel.currency);
|
||||
}
|
||||
if (srvModel.buttonType === '0') {
|
||||
if (srvModel.price) html += addInput(priceInputName, srvModel.price);
|
||||
if (allowCurrencySelection) html += addInput("currency", srvModel.currency);
|
||||
}
|
||||
// Custom amount
|
||||
else if (isCustomAmount) {
|
||||
else if (srvModel.buttonType === '1') {
|
||||
html += ' <div class="btcpay-custom-container">\n <div class="btcpay-custom">\n';
|
||||
html += srvModel.simpleInput ? '' : addPlusMinusButton("-", srvModel.step, srvModel.min, srvModel.max);
|
||||
if (srvModel.price)
|
||||
html += ' ' + addInputPrice(priceInputName, srvModel.price, widthInput, "", "number", srvModel.min, srvModel.max, srvModel.step);
|
||||
html += addInputPrice(priceInputName, srvModel.price, widthInput, srvModel.min, srvModel.max, srvModel.step);
|
||||
html += srvModel.simpleInput ? '' : addPlusMinusButton("+", srvModel.step, srvModel.min, srvModel.max);
|
||||
html += ' </div>\n';
|
||||
if(allowCurrencySelection) {
|
||||
html += addSelectCurrency(srvModel.currency);
|
||||
}
|
||||
if (allowCurrencySelection) html += addSelectCurrency(srvModel.currency);
|
||||
html += ' </div>\n';
|
||||
}
|
||||
// Slider
|
||||
else if (isSlider) {
|
||||
var step = srvModel.step =="any"? 1: srvModel.step;
|
||||
var min = srvModel.min == null? 1: parseInt(srvModel.min);
|
||||
var max = srvModel.max == null? 1: parseInt(srvModel.max);
|
||||
var onChange = "var el=document.querySelector(\'#btcpay-input-price\'); var price = parseInt(el.value); if(price< "+min+") { el.value = "+min+"} else if(price> "+max+") { el.value = "+max+"} document.querySelector(\'#btcpay-input-range\').value = el.value"
|
||||
else if (srvModel.buttonType === '2') {
|
||||
const step = srvModel.step === 'any' ? 1 : srvModel.step;
|
||||
const min = srvModel.min == null ? 1 : parseInt(srvModel.min);
|
||||
const max = srvModel.max == null ? null : parseInt(srvModel.max);
|
||||
|
||||
html += ' <div class="btcpay-custom-container">\n';
|
||||
html += addInputPrice(priceInputName, srvModel.price, width, 'onchange= \"'+onChange+'\"');
|
||||
if(allowCurrencySelection) {
|
||||
html += addSelectCurrency(srvModel.currency);
|
||||
}
|
||||
html += addInputPrice(priceInputName, srvModel.price, width, min, max, step, 'handleSliderChange(event);return false');
|
||||
if (allowCurrencySelection) html += addSelectCurrency(srvModel.currency);
|
||||
html += addSlider(srvModel.price, srvModel.min, srvModel.max, srvModel.step, width);
|
||||
html += ' </div>\n';
|
||||
}
|
||||
|
||||
if(!srvModel.payButtonText){
|
||||
html += ' <input type="image" class="submit" name="submit" src="' + esc(srvModel.payButtonImageUrl) + '" style="width:' + width + '" alt="Pay with BtcPay, Self-Hosted Bitcoin Payment Processor">\n';
|
||||
}else{
|
||||
var numheight = parseInt(height.replace("px", ""));
|
||||
html+= '<button type="submit" class="submit" name="submit" style="min-width:' + width + '; min-height:' + height + '; border-radius: 4px;border-style: none;background-color: #0f3b21;" alt="Pay with BtcPay, Self-Hosted Bitcoin Payment Processor"><span style="color:#fff">'+esc(srvModel.payButtonText)+'</span>\n' +
|
||||
(srvModel.payButtonImageUrl? '<img src="'+esc(srvModel.payButtonImageUrl)+'" style="height:'+numheight+'px;display:inline-block;padding: 5% 0 5% 5px;vertical-align: middle;">\n' : '')+
|
||||
'</button>'
|
||||
}
|
||||
html += srvModel.payButtonText
|
||||
? `<button type="submit" class="submit" name="submit" style="min-width:${width};min-height:${height};border-radius:4px;border-style:none;background-color:#0f3b21;" title="Pay with BTCPay Server, a Self-Hosted Bitcoin Payment Processor"><span style="color:#fff">${esc(srvModel.payButtonText)}</span>\n` +
|
||||
(srvModel.payButtonImageUrl? `<img src="${esc(srvModel.payButtonImageUrl)}" style="height:${parseInt(height.replace('px', ''))}px;display:inline-block;padding:5% 0 5% 5px;vertical-align:middle;">\n` : '') +
|
||||
'</button>'
|
||||
: ` <input type="image" class="submit" name="submit" src="${esc(srvModel.payButtonImageUrl)}" style="width:${width}" alt="Pay with BTCPay Server, a Self-Hosted Bitcoin Payment Processor">\n`;
|
||||
html += '</form>';
|
||||
|
||||
// Scripts
|
||||
var scripts = getScripts(srvModel);
|
||||
var code = html + (scripts ? `\n<script>\n ${scripts.trim()}\n</script>` : '')
|
||||
const scripts = getScripts(srvModel);
|
||||
const code = html + (scripts.length ? `\n<script>\n ${scripts.join('').trim()}\n</script>` : '')
|
||||
|
||||
$("#mainCode").text(code).html();
|
||||
var preview = document.getElementById('preview');
|
||||
const preview = document.getElementById('preview');
|
||||
preview.innerHTML = html;
|
||||
if (scripts) {
|
||||
scripts.forEach(snippet => {
|
||||
// script needs to be inserted as node, otherwise it won't get executed
|
||||
var script = document.createElement('script');
|
||||
script.innerHTML = scripts
|
||||
const script = document.createElement('script')
|
||||
script.innerHTML = snippet
|
||||
preview.appendChild(script)
|
||||
}
|
||||
var form = preview.querySelector("form");
|
||||
var url = new URL(form.getAttribute("action"));
|
||||
var formData = new FormData(form);
|
||||
})
|
||||
const form = preview.querySelector("form");
|
||||
const formData = new FormData(form);
|
||||
let url = new URL(form.getAttribute("action"));
|
||||
formData.forEach((value, key) => {
|
||||
if (key !== "jsonResponse") {
|
||||
url.searchParams.append(key, value);
|
||||
@@ -193,43 +191,36 @@ function inputChanges(event, buttonSize) {
|
||||
}
|
||||
|
||||
function addInput(name, value) {
|
||||
return ' <input type="hidden" name="' + esc(name) + '" value="' + esc(value) + '" />\n';
|
||||
return ` <input type="hidden" name="${esc(name)}" value="${esc(value)}" />\n`;
|
||||
}
|
||||
|
||||
function addPlusMinusButton(type, step, min, max) {
|
||||
step = step =="any"? 1: step;
|
||||
min = min == null? 1: parseInt(min);
|
||||
max = max == null? 1: parseInt(max);
|
||||
var onChange = "event.preventDefault(); var el=document.querySelector(\'#btcpay-input-price\'); var price = parseInt(el.value);"
|
||||
if(type == "-"){
|
||||
onChange += " if((price - "+step+" )< "+min+") { el.value = "+min+"} else {el.value = parseInt(el.value) - "+step + " }";
|
||||
} else if(type == "+"){
|
||||
onChange += " if((price + "+step+" )> "+max+") { el.value = "+max+"} else {el.value = parseInt(el.value) + "+step + " }";
|
||||
}
|
||||
step = step === "any" ? 1 : step;
|
||||
min = min == null ? 1 : parseInt(min);
|
||||
max = max == null ? null : parseInt(max);
|
||||
|
||||
|
||||
return ' <button class="plus-minus" onclick="'+onChange+'">' + type + '</button>\n';
|
||||
}
|
||||
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`;
|
||||
}
|
||||
|
||||
function addInputPrice(name, price, widthInput, customFn, type, min, max, step) {
|
||||
return ' <input id="btcpay-input-price" name="'+name+'" type="' + (type || "text") + '" min="' + (min || 0) + '" max="' + (max || "none") + '" step="' + (step || "any") + '" value="' + price + '" style="width: ' + widthInput + ';" oninput="event.preventDefault();isNaN(event.target.value)? document.querySelector(\'#btcpay-input-price\').value = ' + price + ' : event.target.value; if (this.value < '+min+') {this.value = '+min+'; } else if(this.value > '+max+'){ this.value = '+max+';}" ' + (customFn || '') + ' />\n';
|
||||
function addInputPrice(name, price, widthInput, min = 0, max = 'none', step = 'any', onChange = null) {
|
||||
if (!price) price = min
|
||||
return ` <input id="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`;
|
||||
}
|
||||
|
||||
function addSlider(price, min, max, step, width) {
|
||||
if (!price) price = min
|
||||
return ` <input id="btcpay-input-range" 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`;
|
||||
}
|
||||
|
||||
function addSelectCurrency(currency) {
|
||||
// Remove all non-alphabet characters from input string and uppercase it for display
|
||||
var safeCurrency = currency.replace(/[^a-z]/gi, '').toUpperCase();
|
||||
var defaultCurrencies = ['USD', 'GBP', 'EUR', 'BTC'];
|
||||
var options = defaultCurrencies.map(c => ' <option value="' + c + '"' + (c === safeCurrency ? ' selected' : '') + '>' + c + '</option>');
|
||||
const safeCurrency = currency.replace(/[^a-z]/gi, '').toUpperCase();
|
||||
const defaultCurrencies = ['USD', 'GBP', 'EUR', 'BTC'];
|
||||
const options = defaultCurrencies.map(c => ` <option value="${c}"${(c === safeCurrency ? ' selected' : '')}>${c}</option>`);
|
||||
// If user provided a currency not in our default currencies list, add it to the top of the options as a selected option
|
||||
if (defaultCurrencies.indexOf(safeCurrency) === -1) {
|
||||
options.unshift(' <option value="' + safeCurrency + '" selected>' + safeCurrency + '</option>')
|
||||
options.unshift(` <option value="${safeCurrency}" selected>${safeCurrency}</option>`)
|
||||
}
|
||||
|
||||
return ' <select name="currency">\n' +
|
||||
options.join('\n') + '\n' +
|
||||
' </select>\n'
|
||||
}
|
||||
|
||||
function addSlider(price, min, max, step, width) {
|
||||
return ' <input class="btcpay-input-range" id="btcpay-input-range" value="' + price + '" type="range" min="' + min + '" max="' + max + '" step="' + step + '" style="width:' + width + ';margin-bottom:15px;" oninput="document.querySelector(\'#btcpay-input-price\').value = document.querySelector(\'#btcpay-input-range\').value" />\n';
|
||||
return ` <select name="currency">\n${options.join('\n')}\n </select>\n`
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user