diff --git a/app/javascript/packs/application.js b/app/javascript/packs/application.js index b0e081f98..184c7c6df 100644 --- a/app/javascript/packs/application.js +++ b/app/javascript/packs/application.js @@ -39,6 +39,7 @@ import { } from '../dashboard/helper/scriptHelpers'; import FluentIcon from 'shared/components/FluentIcon/DashboardIcon'; import VueDOMPurifyHTML from 'vue-dompurify-html'; +import { domPurifyConfig } from '../shared/helpers/HTMLSanitizer'; Vue.config.env = process.env; @@ -55,7 +56,8 @@ if (window.analyticsConfig) { api_host: window.analyticsConfig.host, }); } -Vue.use(VueDOMPurifyHTML); + +Vue.use(VueDOMPurifyHTML, domPurifyConfig); Vue.use(VueRouter); Vue.use(VueI18n); Vue.use(WootUiKit); diff --git a/app/javascript/packs/widget.js b/app/javascript/packs/widget.js index 3b9661bc7..6eab2db8b 100644 --- a/app/javascript/packs/widget.js +++ b/app/javascript/packs/widget.js @@ -9,9 +9,10 @@ import ActionCableConnector from '../widget/helpers/actionCable'; import i18n from '../widget/i18n'; import { isPhoneE164OrEmpty } from 'shared/helpers/Validators'; import router from '../widget/router'; +import { domPurifyConfig } from '../shared/helpers/HTMLSanitizer'; Vue.use(VueI18n); Vue.use(Vuelidate); -Vue.use(VueDOMPurifyHTML); +Vue.use(VueDOMPurifyHTML, domPurifyConfig); const i18nConfig = new VueI18n({ locale: 'en', diff --git a/app/javascript/shared/helpers/HTMLSanitizer.js b/app/javascript/shared/helpers/HTMLSanitizer.js index b119b3681..889948c0b 100644 --- a/app/javascript/shared/helpers/HTMLSanitizer.js +++ b/app/javascript/shared/helpers/HTMLSanitizer.js @@ -6,3 +6,15 @@ export const escapeHtml = (unsafe = '') => { .replace(/"/g, '"') .replace(/'/g, '''); }; + +export const afterSanitizeAttributes = currentNode => { + if ('target' in currentNode) { + currentNode.setAttribute('target', '_blank'); + } +}; + +export const domPurifyConfig = { + hooks: { + afterSanitizeAttributes, + }, +}; diff --git a/app/javascript/shared/helpers/MessageFormatter.js b/app/javascript/shared/helpers/MessageFormatter.js index 260436da4..c4d0bb62e 100644 --- a/app/javascript/shared/helpers/MessageFormatter.js +++ b/app/javascript/shared/helpers/MessageFormatter.js @@ -1,6 +1,6 @@ import { marked } from 'marked'; import DOMPurify from 'dompurify'; -import { escapeHtml } from './HTMLSanitizer'; +import { escapeHtml, afterSanitizeAttributes } from './HTMLSanitizer'; const TWITTER_USERNAME_REGEX = /(^|[^@\w])@(\w{1,15})\b/g; const TWITTER_USERNAME_REPLACEMENT = @@ -48,9 +48,7 @@ class MessageFormatter { const markedDownOutput = marked(withHash); return markedDownOutput; } - DOMPurify.addHook('afterSanitizeAttributes', node => { - if ('target' in node) node.setAttribute('target', '_blank'); - }); + DOMPurify.addHook('afterSanitizeAttributes', afterSanitizeAttributes); return DOMPurify.sanitize( marked(this.message, { breaks: true, gfm: true }) ); diff --git a/package.json b/package.json index d418d0b68..f862e0fbe 100644 --- a/package.json +++ b/package.json @@ -63,7 +63,7 @@ "vue-chartjs": "3.5.1", "vue-clickaway": "~2.1.0", "vue-color": "2.8.1", - "vue-dompurify-html": "^2.5.1", + "vue-dompurify-html": "^2.5.2", "vue-easytable": "2.5.5", "vue-i18n": "8.24.3", "vue-loader": "15.9.6", diff --git a/yarn.lock b/yarn.lock index 82d266618..fa46ddb37 100644 --- a/yarn.lock +++ b/yarn.lock @@ -15284,10 +15284,10 @@ vue-docgen-loader@^1.5.0: loader-utils "^1.2.3" querystring "^0.2.0" -vue-dompurify-html@^2.5.1: - version "2.5.1" - resolved "https://registry.npmjs.org/vue-dompurify-html/-/vue-dompurify-html-2.5.1.tgz#a754f4ac7b18eb8fe41f461cb2bb1c4956a9bd2d" - integrity sha512-B8rQj2jAPJJhtKHHa6jg5B3/RoKBmmUl/awP/GxWXGu75j4Y7+MHqv0DG52v0Uz0taEpHyZun34KEYMAfrPWnA== +vue-dompurify-html@^2.5.2: + version "2.5.2" + resolved "https://registry.yarnpkg.com/vue-dompurify-html/-/vue-dompurify-html-2.5.2.tgz#f547d4eacae4640f95eb0e9308e7ef8e223887c6" + integrity sha512-G6I135+BhlACJ9xftqK7fvhXyjNrgHCI594qHnUW5e2Bmp8BOTV1kz7cxwI37b4BJnHkj9IY10RwMPOtJqw+pw== dependencies: dompurify "^2.3.4"