Allowed markdown in footer of status page

Markdown support has been added using the marked module. To secure
against XSS attacks, DOMPurify is used to sanitize the generated HTML
before it is loaded on the page.

Signed-off-by: Matthew Nickson <mnickson@sidingsmedia.com>
This commit is contained in:
Matthew Nickson 2023-01-08 20:39:27 +00:00
parent f3d3e064f8
commit 6bc0bd84af
No known key found for this signature in database
GPG key ID: BF229DCFD4748E05
4 changed files with 42 additions and 1 deletions

28
package-lock.json generated
View file

@ -26,6 +26,7 @@
"compare-versions": "~3.6.0", "compare-versions": "~3.6.0",
"compression": "~1.7.4", "compression": "~1.7.4",
"dayjs": "~1.11.5", "dayjs": "~1.11.5",
"dompurify": "^2.4.3",
"express": "~4.17.3", "express": "~4.17.3",
"express-basic-auth": "~1.2.1", "express-basic-auth": "~1.2.1",
"express-static-gzip": "~2.1.7", "express-static-gzip": "~2.1.7",
@ -38,6 +39,7 @@
"jsonwebtoken": "~9.0.0", "jsonwebtoken": "~9.0.0",
"jwt-decode": "~3.1.2", "jwt-decode": "~3.1.2",
"limiter": "~2.1.0", "limiter": "~2.1.0",
"marked": "^4.2.5",
"mqtt": "~4.3.7", "mqtt": "~4.3.7",
"mssql": "~8.1.4", "mssql": "~8.1.4",
"mysql2": "~2.3.3", "mysql2": "~2.3.3",
@ -6499,6 +6501,11 @@
"url": "https://github.com/fb55/domhandler?sponsor=1" "url": "https://github.com/fb55/domhandler?sponsor=1"
} }
}, },
"node_modules/dompurify": {
"version": "2.4.3",
"resolved": "https://registry.npmjs.org/dompurify/-/dompurify-2.4.3.tgz",
"integrity": "sha512-q6QaLcakcRjebxjg8/+NP+h0rPfatOgOzc46Fst9VAA3jF2ApfKBNKMzdP4DYTqtUMXSCd5pRS/8Po/OmoCHZQ=="
},
"node_modules/domutils": { "node_modules/domutils": {
"version": "3.0.1", "version": "3.0.1",
"resolved": "https://registry.npmjs.org/domutils/-/domutils-3.0.1.tgz", "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.0.1.tgz",
@ -12185,6 +12192,17 @@
"url": "https://github.com/sponsors/sindresorhus" "url": "https://github.com/sponsors/sindresorhus"
} }
}, },
"node_modules/marked": {
"version": "4.2.5",
"resolved": "https://registry.npmjs.org/marked/-/marked-4.2.5.tgz",
"integrity": "sha512-jPueVhumq7idETHkb203WDD4fMA3yV9emQ5vLwop58lu8bTclMghBWcYAavlDqIEMaisADinV1TooIFCfqOsYQ==",
"bin": {
"marked": "bin/marked.js"
},
"engines": {
"node": ">= 12"
}
},
"node_modules/mathml-tag-names": { "node_modules/mathml-tag-names": {
"version": "2.1.3", "version": "2.1.3",
"resolved": "https://registry.npmjs.org/mathml-tag-names/-/mathml-tag-names-2.1.3.tgz", "resolved": "https://registry.npmjs.org/mathml-tag-names/-/mathml-tag-names-2.1.3.tgz",
@ -22155,6 +22173,11 @@
"domelementtype": "^2.3.0" "domelementtype": "^2.3.0"
} }
}, },
"dompurify": {
"version": "2.4.3",
"resolved": "https://registry.npmjs.org/dompurify/-/dompurify-2.4.3.tgz",
"integrity": "sha512-q6QaLcakcRjebxjg8/+NP+h0rPfatOgOzc46Fst9VAA3jF2ApfKBNKMzdP4DYTqtUMXSCd5pRS/8Po/OmoCHZQ=="
},
"domutils": { "domutils": {
"version": "3.0.1", "version": "3.0.1",
"resolved": "https://registry.npmjs.org/domutils/-/domutils-3.0.1.tgz", "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.0.1.tgz",
@ -26299,6 +26322,11 @@
"integrity": "sha512-hdN1wVrZbb29eBGiGjJbeP8JbKjq1urkHJ/LIP/NY48MZ1QVXUsQBV1G1zvYFHn1XE06cwjBsOI2K3Ulnj1YXQ==", "integrity": "sha512-hdN1wVrZbb29eBGiGjJbeP8JbKjq1urkHJ/LIP/NY48MZ1QVXUsQBV1G1zvYFHn1XE06cwjBsOI2K3Ulnj1YXQ==",
"dev": true "dev": true
}, },
"marked": {
"version": "4.2.5",
"resolved": "https://registry.npmjs.org/marked/-/marked-4.2.5.tgz",
"integrity": "sha512-jPueVhumq7idETHkb203WDD4fMA3yV9emQ5vLwop58lu8bTclMghBWcYAavlDqIEMaisADinV1TooIFCfqOsYQ=="
},
"mathml-tag-names": { "mathml-tag-names": {
"version": "2.1.3", "version": "2.1.3",
"resolved": "https://registry.npmjs.org/mathml-tag-names/-/mathml-tag-names-2.1.3.tgz", "resolved": "https://registry.npmjs.org/mathml-tag-names/-/mathml-tag-names-2.1.3.tgz",

View file

@ -81,6 +81,7 @@
"compare-versions": "~3.6.0", "compare-versions": "~3.6.0",
"compression": "~1.7.4", "compression": "~1.7.4",
"dayjs": "~1.11.5", "dayjs": "~1.11.5",
"dompurify": "^2.4.3",
"express": "~4.17.3", "express": "~4.17.3",
"express-basic-auth": "~1.2.1", "express-basic-auth": "~1.2.1",
"express-static-gzip": "~2.1.7", "express-static-gzip": "~2.1.7",
@ -93,6 +94,7 @@
"jsonwebtoken": "~9.0.0", "jsonwebtoken": "~9.0.0",
"jwt-decode": "~3.1.2", "jwt-decode": "~3.1.2",
"limiter": "~2.1.0", "limiter": "~2.1.0",
"marked": "^4.2.5",
"mqtt": "~4.3.7", "mqtt": "~4.3.7",
"mssql": "~8.1.4", "mssql": "~8.1.4",
"mysql2": "~2.3.3", "mysql2": "~2.3.3",

View file

@ -675,4 +675,5 @@ export default {
"General Monitor Type": "General Monitor Type", "General Monitor Type": "General Monitor Type",
"Passive Monitor Type": "Passive Monitor Type", "Passive Monitor Type": "Passive Monitor Type",
"Specific Monitor Type": "Specific Monitor Type", "Specific Monitor Type": "Specific Monitor Type",
markdownSupported: "Markdown syntax supported",
}; };

View file

@ -26,6 +26,9 @@
<div class="my-3"> <div class="my-3">
<label for="footer-text" class="form-label">{{ $t("Footer Text") }}</label> <label for="footer-text" class="form-label">{{ $t("Footer Text") }}</label>
<textarea id="footer-text" v-model="config.footerText" class="form-control"></textarea> <textarea id="footer-text" v-model="config.footerText" class="form-control"></textarea>
<div class="form-text">
{{ $t("markdownSupported") }}
</div>
</div> </div>
<div class="my-3 form-check form-switch"> <div class="my-3 form-check form-switch">
@ -279,7 +282,9 @@
<div class="custom-footer-text text-start"> <div class="custom-footer-text text-start">
<strong v-if="enableEditMode">{{ $t("Custom Footer") }}:</strong> <strong v-if="enableEditMode">{{ $t("Custom Footer") }}:</strong>
</div> </div>
<Editable v-model="config.footerText" tag="div" :contenteditable="enableEditMode" :noNL="false" class="alert-heading p-2" /> <Editable v-if="enableEditMode" v-model="config.footerText" tag="div" :contenteditable="enableEditMode" :noNL="false" class="alert-heading p-2" />
<!-- eslint-disable-next-line vue/no-v-html-->
<div v-if="! enableEditMode" class="alert-heading p-2" v-html="footerHTML"></div>
<p v-if="config.showPoweredBy"> <p v-if="config.showPoweredBy">
{{ $t("Powered by") }} <a target="_blank" rel="noopener noreferrer" href="https://github.com/louislam/uptime-kuma">{{ $t("Uptime Kuma" ) }}</a> {{ $t("Powered by") }} <a target="_blank" rel="noopener noreferrer" href="https://github.com/louislam/uptime-kuma">{{ $t("Uptime Kuma" ) }}</a>
@ -310,6 +315,8 @@ import ImageCropUpload from "vue-image-crop-upload";
import { PrismEditor } from "vue-prism-editor"; import { PrismEditor } from "vue-prism-editor";
import "vue-prism-editor/dist/prismeditor.min.css"; // import the styles somewhere import "vue-prism-editor/dist/prismeditor.min.css"; // import the styles somewhere
import { useToast } from "vue-toastification"; import { useToast } from "vue-toastification";
import { marked } from "marked";
import DOMPurify from "dompurify";
import Confirm from "../components/Confirm.vue"; import Confirm from "../components/Confirm.vue";
import PublicGroupList from "../components/PublicGroupList.vue"; import PublicGroupList from "../components/PublicGroupList.vue";
import MaintenanceTime from "../components/MaintenanceTime.vue"; import MaintenanceTime from "../components/MaintenanceTime.vue";
@ -477,6 +484,9 @@ export default {
return this.overallStatus === STATUS_PAGE_MAINTENANCE; return this.overallStatus === STATUS_PAGE_MAINTENANCE;
}, },
footerHTML() {
return DOMPurify.sanitize(marked(this.config.footerText));
},
}, },
watch: { watch: {