Skip to content

Commit b3d2a4c

Browse files
authored
Merge pull request #376 from snap-cloud/username-welcome-modal
deploy xss fixes again
2 parents 9846bf5 + 692810a commit b3d2a4c

File tree

4 files changed

+58
-18
lines changed

4 files changed

+58
-18
lines changed

controllers/user.lua

+4-1
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,9 @@ require 'responses'
4040
require 'passwords'
4141
local validations = require('validation')
4242
local assert_current_user_logged_in = validations.assert_current_user_logged_in
43+
-- Local Snap!Cloud functions
44+
local utils = require('lib.util')
45+
local escape_html = utils.escape_html
4346

4447
UserController = {
4548
run_query = function (self, query)
@@ -448,7 +451,7 @@ UserController = {
448451
create_token(self, 'verify_user', user)
449452

450453
return jsonResponse({
451-
message = 'User ' .. self.params.username ..
454+
message = 'User ' .. escape_html(self.params.username) ..
452455
' created.\nPlease check your email and validate your\n' ..
453456
'account within the next 3 days.\nYou can now log in.',
454457
title = 'Account Created',

lib/util.lua

+19-1
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,25 @@ local function domain_name(url)
3434
return url:gsub('https*://', ''):gsub(':%d+$', '')
3535
end
3636

37+
local function escape_html(text)
38+
if text == nil then return end
39+
40+
text = tostring(text)
41+
local map = {
42+
["&"] = "&",
43+
["<"] = "&lt;",
44+
[">"] = "&gt;",
45+
['"'] = "&quot;",
46+
["'"] = "&#039;"
47+
}
48+
49+
return (text:gsub("[&<>\'\"]", function(m)
50+
return map[m]
51+
end))
52+
end
53+
3754
return {
3855
capitalize = capitalize,
39-
domain_name = domain_name
56+
domain_name = domain_name,
57+
escape_html = escape_html,
4058
}

static/js/base.js

+17-16
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,21 @@ function getUrlParameter (param) {
2626
return decodeURIComponent(results[2].replace(/\+/g, ' '));
2727
};
2828

29+
function escapeHtml (text) {
30+
if (text === null || text === undefined) { return; }
31+
32+
if (text.toString) { text = text.toString(); }
33+
// Based on an answer by Kip @ StackOverflow
34+
let map = {
35+
'&': '&amp;',
36+
'<': '&lt;',
37+
'>': '&gt;',
38+
'"': '&quot;',
39+
"'": '&#039;'
40+
};
41+
return text.replace(/[&<>"']/g, (m) => map[m]);
42+
}
43+
2944
// Error handling
3045

3146
function genericError (errorString, title) {
@@ -61,20 +76,6 @@ function doneLoading (selector) {
6176
}
6277
};
6378

64-
// Other goodies
65-
66-
function escapeHtml (text) {
67-
// Based on an answer by Kip @ StackOverflow
68-
var map = {
69-
'&': '&amp;',
70-
'<': '&lt;',
71-
'>': '&gt;',
72-
'"': '&quot;',
73-
"'": '&#039;'
74-
};
75-
return text ? text.replace(/[&<>"']/g, function (m) { return map[m]; }) : ''
76-
};
77-
7879
function enableEnterSubmit () {
7980
// Submits "forms" when enter is pressed on any of their inputs
8081
document.querySelectorAll('.pure-form input').forEach(
@@ -170,7 +171,7 @@ Cloud.prototype.apiRequest = function (method, path, onSuccess, body) {
170171
if (response && response.title) {
171172
alert(
172173
localizer.localize(response.message),
173-
{ title: localizer.localize(response.title) },
174+
{ title: escapeHtml(localizer.localize(response.title)) },
174175
function () { onSuccess.call(this, response) }
175176
);
176177
} else {
@@ -180,7 +181,7 @@ Cloud.prototype.apiRequest = function (method, path, onSuccess, body) {
180181
errorMessage => {
181182
alert(
182183
localizer.localize(errorMessage),
183-
{ title: localizer.localize('Error') },
184+
{ title: escapeHtml(localizer.localize('Error')) },
184185
Cloud.redirect
185186
)
186187
},

views/js/dialog.etlua

+18
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,28 @@
11
<script>
2+
function escapeHtml (text) {
3+
if (text === null || text === undefined) { return; }
4+
5+
if (text.toString) { text = text.toString(); }
6+
// Based on an answer by Kip @ StackOverflow
7+
let map = {
8+
'&': '&amp;',
9+
'<': '&lt;',
10+
'>': '&gt;',
11+
'"': '&quot;',
12+
"'": '&#039;'
13+
};
14+
return text.replace(/[&<>"']/g, (m) => map[m]);
15+
}
16+
217
function dialog (title, body, onSuccess, onCancel, onOpen) {
318
// I reuse CustomAlert's dialogs
419
var dialogBox = onCancel
520
? document.querySelector('#customconfirm')
621
: document.querySelector('#customalert'),
722
bodyDiv = dialogBox.querySelector('.body');
823

24+
title = escapeHtml(title);
25+
926
if (typeof body == 'string') {
1027
bodyDiv.innerHTML = body.replaceAll('\n', '<br>');
1128
} else {
@@ -116,6 +133,7 @@ document.onkeyup = function (event) {
116133
};
117134

118135
// custom-alert.min.js
136+
// TODO: de-minify and add HTML escaping.
119137
function customAlert(a){function c(a,b){for(var c in b)a[c]=b[c];return a}function d(){var a={button:"OK",title:"Alert"};this.render=function(b,d){this.options=d?c(a,d):a;var e=document.querySelector("#customalert");e.querySelector(".header").innerHTML=this.options.title,e.querySelector(".body").innerHTML=b,e.querySelector(".button-done").innerHTML=this.options.button,document.querySelector("html").style.overflow="hidden",document.querySelector("#customalert-overlay").style.display="block",e.style.display="block"},this.done=function(){document.querySelector("#customalert").style.display=null,document.querySelector("#customalert-overlay").style.display=null,document.querySelector("html").style.overflow="auto","function"==typeof this.callback&&this.callback.call()}}function e(){var a={done:{text:"<%= locale.get('ok') %>",bold:!1,default:!0},cancel:{text:"<%= locale.get('cancel') %>",bold:!1,default:!1},title:"<%= locale.get('confirm') %>"},b=function(a,b){return a[b].bold?"<strong>"+a[b].text+"</strong>":a[b].text};this.callback=function(a){},this.render=function(d,e){this.options=a,e&&(e.done&&"string"==typeof e.done&&(e.done={text:e.done}),e.cancel&&"string"==typeof e.cancel&&(e.cancel={text:e.cancel}),1==e.cancel.default?e.done.default=!1:e.done.default=!0,e.cancel&&(this.options.cancel=c(a.cancel,e.cancel)),e.done&&(this.options.done=c(a.done,e.done)),e.title&&(this.options.title=e.title));var f=document.querySelector("#customconfirm");f.querySelector(".header").innerHTML=this.options.title,f.querySelector(".body").innerHTML=d,f.querySelector(".button-cancel").innerHTML=b(this.options,"cancel"),f.querySelector(".button-done").innerHTML=b(this.options,"done"),document.querySelector("html").style.overflow="hidden",document.querySelector("#customconfirm-overlay").style.display="block",f.style.display="block"},this.done=function(){if(this.end(),this.callbackSuccess)return this.callbackSuccess();this.callback(!0)},this.cancel=function(){if(this.end(),this.callbackCancel)return this.callbackCancel();this.callback(!1)},this.end=function(){document.querySelector("#customconfirm").style.display="none",document.querySelector("#customconfirm-overlay").style.display="none",document.querySelector("html").style.overflow="auto"}}var f,g,b=function(a){return this.el=document.createElement(a),this.attr=function(a){var b=this;for(var c in a)b.el.setAttribute(c,a[c]);return b},this.parent=function(a,b){return b=b?document.querySelector(b):document,a=b.querySelector(a),a.appendChild(this.el),this},this.html=function(a){return this.el.innerHTML=a,this},this};if(null==document.getElementById("customalert")&&(b("div").attr({id:"customalert-overlay",class:"customalert-overlay"}).parent("body"),b("div").attr({id:"customalert",class:"customalert customalert-alert"}).parent("body"),b("div").attr({class:"header"}).parent("#customalert"),b("div").attr({class:"body"}).parent("#customalert"),b("div").attr({class:"footer"}).parent("#customalert"),b("button").attr({class:"pure-button btn btn-primary custom-alert button-done",onclick:"window.customalert.done()"}).parent(".footer","#customalert"),window.addEventListener("keydown",function(a){var b=document.getElementById("customconfirm"),c=document.getElementById("customalert");if(!(null==c&&null==b||null!=b&&"block"!=b.style.display||null!=c&&"block"!=c.style.display)){a.preventDefault(),a.stopPropagation();var d=a.keyCode?a.keyCode:a.which;13==d?"block"==b.style.display?window.customconfirm.options.cancel.default?window.customconfirm.cancel():window.customconfirm.done():"block"==c.style.display&&window.customalert.done():27==d&&"block"==b.style.display&&window.customconfirm.cancel()}},!1),f=window.Alert=function(a,b,c){window.customalert=new d,"function"==typeof b?(window.customalert.callback=b,b=null):window.customalert.callback=c||null,window.customalert.render(a,b)}),null==document.getElementById("customconfirm")&&(b("div").attr({id:"customconfirm-overlay",class:"customalert-overlay"}).parent("body"),b("div").attr({id:"customconfirm",class:"customalert customalert-confirm"}).parent("body"),b("div").attr({class:"header"}).parent("#customconfirm"),b("div").attr({class:"body"}).parent("#customconfirm"),b("div").attr({class:"footer"}).parent("#customconfirm"),b("button").attr({class:"pure-button btn btn-danger button-cancel",onclick:"window.customconfirm.cancel()"}).parent(".footer","#customconfirm"),b("button").attr({class:"pure-button btn btn-success custom-alert button-done",onclick:"window.customconfirm.done()"}).parent(".footer","#customconfirm"),g=window.Confirm=function(a,b,c){window.customconfirm=new e,"object"==typeof b?(window.customconfirm.callbackSuccess=b.success,window.customconfirm.callbackCancel=b.cancel):window.customconfirm.callback=b,window.customconfirm.render(a,c)}),!1===a)return{alert:f,confirm:g};window.alert=f;
120138
// old_confirm had very strange behavior...
121139
window.confirm = function(text, onSuccess, params) { g(text, ok => { if (ok) { onSuccess.call(params); } }); }; }

0 commit comments

Comments
 (0)