Sanitize HTML strings passed to buefy.toast().
The buefy toast component does not sanitize HTML leaving it open to XSS. This patch centralised all toast calls in the app to a util function which sanitizes HTML strings before passing to toast(). Closes #357.
This commit is contained in:
parent
cf0c8f3855
commit
ed57ecca99
|
@ -9,6 +9,17 @@ dayjs.extend(relativeTime);
|
||||||
|
|
||||||
const reEmail = /(.+?)@(.+?)/ig;
|
const reEmail = /(.+?)@(.+?)/ig;
|
||||||
|
|
||||||
|
const htmlEntities = {
|
||||||
|
'&': '&',
|
||||||
|
'<': '<',
|
||||||
|
'>': '>',
|
||||||
|
'"': '"',
|
||||||
|
"'": ''',
|
||||||
|
'/': '/',
|
||||||
|
'`': '`',
|
||||||
|
'=': '=',
|
||||||
|
};
|
||||||
|
|
||||||
export default class Utils {
|
export default class Utils {
|
||||||
constructor(i18n) {
|
constructor(i18n) {
|
||||||
this.i18n = i18n;
|
this.i18n = i18n;
|
||||||
|
@ -67,6 +78,9 @@ export default class Utils {
|
||||||
return out.toFixed(2) + pfx;
|
return out.toFixed(2) + pfx;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// https://stackoverflow.com/a/12034334
|
||||||
|
escapeHTML = (html) => html.replace(/[&<>"'`=/]/g, (s) => htmlEntities[s]);
|
||||||
|
|
||||||
// UI shortcuts.
|
// UI shortcuts.
|
||||||
confirm = (msg, onConfirm, onCancel) => {
|
confirm = (msg, onConfirm, onCancel) => {
|
||||||
Dialog.confirm({
|
Dialog.confirm({
|
||||||
|
@ -98,7 +112,7 @@ export default class Utils {
|
||||||
|
|
||||||
toast = (msg, typ, duration) => {
|
toast = (msg, typ, duration) => {
|
||||||
Toast.open({
|
Toast.open({
|
||||||
message: msg,
|
message: this.escapeHTML(msg),
|
||||||
type: !typ ? 'is-success' : typ,
|
type: !typ ? 'is-success' : typ,
|
||||||
queue: false,
|
queue: false,
|
||||||
duration: duration || 2000,
|
duration: duration || 2000,
|
||||||
|
|
|
@ -278,11 +278,7 @@ export default Vue.extend({
|
||||||
// Post.
|
// Post.
|
||||||
this.$api.importSubscribers(params).then(() => {
|
this.$api.importSubscribers(params).then(() => {
|
||||||
// On file upload, show a confirmation.
|
// On file upload, show a confirmation.
|
||||||
this.$buefy.toast.open({
|
this.$utils.toast(this.$t('import.importStarted'));
|
||||||
message: this.$t('import.importStarted'),
|
|
||||||
type: 'is-success',
|
|
||||||
queue: false,
|
|
||||||
});
|
|
||||||
|
|
||||||
// Start polling status.
|
// Start polling status.
|
||||||
this.pollStatus();
|
this.pollStatus();
|
||||||
|
|
|
@ -84,11 +84,7 @@ export default Vue.extend({
|
||||||
this.$api.createList(this.form).then((data) => {
|
this.$api.createList(this.form).then((data) => {
|
||||||
this.$emit('finished');
|
this.$emit('finished');
|
||||||
this.$parent.close();
|
this.$parent.close();
|
||||||
this.$buefy.toast.open({
|
this.$utils.toast(this.$t('globals.messages.created', { name: data.name }));
|
||||||
message: this.$t('globals.messages.created', { name: data.name }),
|
|
||||||
type: 'is-success',
|
|
||||||
queue: false,
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -96,11 +92,7 @@ export default Vue.extend({
|
||||||
this.$api.updateList({ id: this.data.id, ...this.form }).then((data) => {
|
this.$api.updateList({ id: this.data.id, ...this.form }).then((data) => {
|
||||||
this.$emit('finished');
|
this.$emit('finished');
|
||||||
this.$parent.close();
|
this.$parent.close();
|
||||||
this.$buefy.toast.open({
|
this.$utils.toast(this.$t('globals.messages.updated', { name: data.name }));
|
||||||
message: this.$t('globals.messages.updated', { name: data.name }),
|
|
||||||
type: 'is-success',
|
|
||||||
queue: false,
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
|
@ -181,11 +181,7 @@ export default Vue.extend({
|
||||||
this.$api.deleteList(list.id).then(() => {
|
this.$api.deleteList(list.id).then(() => {
|
||||||
this.getLists();
|
this.getLists();
|
||||||
|
|
||||||
this.$buefy.toast.open({
|
this.$utils.toast(this.$t('globals.messages.deleted', { name: list.name }));
|
||||||
message: this.$t('globals.messages.deleted', { name: list.name }),
|
|
||||||
type: 'is-success',
|
|
||||||
queue: false,
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
|
@ -119,11 +119,7 @@ export default Vue.extend({
|
||||||
this.$api.createSubscriber(data).then((d) => {
|
this.$api.createSubscriber(data).then((d) => {
|
||||||
this.$emit('finished');
|
this.$emit('finished');
|
||||||
this.$parent.close();
|
this.$parent.close();
|
||||||
this.$buefy.toast.open({
|
this.$utils.toast(this.$t('globals.messages.created', { name: d.name }));
|
||||||
message: this.$t('globals.messages.created', { name: d.name }),
|
|
||||||
type: 'is-success',
|
|
||||||
queue: false,
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -150,11 +146,7 @@ export default Vue.extend({
|
||||||
this.$api.updateSubscriber(data).then((d) => {
|
this.$api.updateSubscriber(data).then((d) => {
|
||||||
this.$emit('finished');
|
this.$emit('finished');
|
||||||
this.$parent.close();
|
this.$parent.close();
|
||||||
this.$buefy.toast.open({
|
this.$utils.toast(this.$t('globals.messages.updated', { name: d.name }));
|
||||||
message: this.$t('globals.messages.updated', { name: d.name }),
|
|
||||||
type: 'is-success',
|
|
||||||
queue: false,
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -164,21 +156,12 @@ export default Vue.extend({
|
||||||
try {
|
try {
|
||||||
attribs = JSON.parse(str);
|
attribs = JSON.parse(str);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
this.$buefy.toast.open({
|
this.$utils.toast(`${this.$t('subscribers.invalidJSON')}: ${e.toString()}`,
|
||||||
message: `${this.$t('subscribers.invalidJSON')}: ${e.toString()}`,
|
'is-danger', 3000);
|
||||||
type: 'is-danger',
|
|
||||||
duration: 3000,
|
|
||||||
queue: false,
|
|
||||||
});
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
if (attribs instanceof Array) {
|
if (attribs instanceof Array) {
|
||||||
this.$buefy.toast.open({
|
this.$utils.toast('Attributes should be a map {} and not an array []', 'is-danger', 3000);
|
||||||
message: 'Attributes should be a map {} and not an array []',
|
|
||||||
type: 'is-danger',
|
|
||||||
duration: 3000,
|
|
||||||
queue: false,
|
|
||||||
});
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -355,11 +355,7 @@ export default Vue.extend({
|
||||||
this.$api.deleteSubscriber(sub.id).then(() => {
|
this.$api.deleteSubscriber(sub.id).then(() => {
|
||||||
this.querySubscribers();
|
this.querySubscribers();
|
||||||
|
|
||||||
this.$buefy.toast.open({
|
this.$utils.toast(this.$t('globals.messages.deleted', { name: sub.name }));
|
||||||
message: this.$t('globals.messages.deleted', { name: sub.name }),
|
|
||||||
type: 'is-success',
|
|
||||||
queue: false,
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
@ -406,11 +402,7 @@ export default Vue.extend({
|
||||||
.then(() => {
|
.then(() => {
|
||||||
this.querySubscribers();
|
this.querySubscribers();
|
||||||
|
|
||||||
this.$buefy.toast.open({
|
this.$utils.toast(this.$t('subscribers.subscribersDeleted', { num: this.numSelectedSubscribers }));
|
||||||
message: this.$t('subscribers.subscribersDeleted', { num: this.numSelectedSubscribers }),
|
|
||||||
type: 'is-success',
|
|
||||||
queue: false,
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
|
@ -422,11 +414,8 @@ export default Vue.extend({
|
||||||
}).then(() => {
|
}).then(() => {
|
||||||
this.querySubscribers();
|
this.querySubscribers();
|
||||||
|
|
||||||
this.$buefy.toast.open({
|
this.$utils.toast(this.$t('subscribers.subscribersDeleted',
|
||||||
message: this.$t('subscribers.subscribersDeleted', { num: this.numSelectedSubscribers }),
|
{ num: this.numSelectedSubscribers }));
|
||||||
type: 'is-success',
|
|
||||||
queue: false,
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -454,11 +443,7 @@ export default Vue.extend({
|
||||||
|
|
||||||
fn(data).then(() => {
|
fn(data).then(() => {
|
||||||
this.querySubscribers();
|
this.querySubscribers();
|
||||||
this.$buefy.toast.open({
|
this.$utils.toast(this.$t('subscribers.listChangeApplied'));
|
||||||
message: this.$t('subscribers.listChangeApplied'),
|
|
||||||
type: 'is-success',
|
|
||||||
queue: false,
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
|
@ -98,11 +98,7 @@ export default Vue.extend({
|
||||||
this.$api.createTemplate(data).then((d) => {
|
this.$api.createTemplate(data).then((d) => {
|
||||||
this.$emit('finished');
|
this.$emit('finished');
|
||||||
this.$parent.close();
|
this.$parent.close();
|
||||||
this.$buefy.toast.open({
|
this.$utils.toast(this.$t('globals.messages.created', { name: d.name }));
|
||||||
message: this.$t('globals.messages.created', { name: d.name }),
|
|
||||||
type: 'is-success',
|
|
||||||
queue: false,
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -116,11 +112,7 @@ export default Vue.extend({
|
||||||
this.$api.updateTemplate(data).then((d) => {
|
this.$api.updateTemplate(data).then((d) => {
|
||||||
this.$emit('finished');
|
this.$emit('finished');
|
||||||
this.$parent.close();
|
this.$parent.close();
|
||||||
this.$buefy.toast.open({
|
this.$utils.toast(`'${d.name}' updated`);
|
||||||
message: `'${d.name}' updated`,
|
|
||||||
type: 'is-success',
|
|
||||||
queue: false,
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
|
@ -143,35 +143,21 @@ export default Vue.extend({
|
||||||
this.$api.createTemplate(data).then((d) => {
|
this.$api.createTemplate(data).then((d) => {
|
||||||
this.$api.getTemplates();
|
this.$api.getTemplates();
|
||||||
this.$emit('finished');
|
this.$emit('finished');
|
||||||
this.$buefy.toast.open({
|
this.$utils.toast(`'${d.name}' created`);
|
||||||
message: `'${d.name}' created`,
|
|
||||||
type: 'is-success',
|
|
||||||
queue: false,
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
makeTemplateDefault(tpl) {
|
makeTemplateDefault(tpl) {
|
||||||
this.$api.makeTemplateDefault(tpl.id).then(() => {
|
this.$api.makeTemplateDefault(tpl.id).then(() => {
|
||||||
this.$api.getTemplates();
|
this.$api.getTemplates();
|
||||||
|
this.$utils.toast(this.$t('globals.messages.created', { name: tpl.name }));
|
||||||
this.$buefy.toast.open({
|
|
||||||
message: this.$t('globals.messages.created', { name: tpl.name }),
|
|
||||||
type: 'is-success',
|
|
||||||
queue: false,
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
deleteTemplate(tpl) {
|
deleteTemplate(tpl) {
|
||||||
this.$api.deleteTemplate(tpl.id).then(() => {
|
this.$api.deleteTemplate(tpl.id).then(() => {
|
||||||
this.$api.getTemplates();
|
this.$api.getTemplates();
|
||||||
|
this.$utils.toast(this.$t('globals.messages.deleted', { name: tpl.name }));
|
||||||
this.$buefy.toast.open({
|
|
||||||
message: this.$t('globals.messages.deleted', { name: tpl.name }),
|
|
||||||
type: 'is-success',
|
|
||||||
queue: false,
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
Loading…
Reference in New Issue