Browse Source

feat(News): sanitizing of html output of markdown

Signed-off-by: Jonathan <theflametrooper@gmail.com>
Jonathan 3 years ago
parent
commit
0402848da0

+ 5 - 0
frontend/package-lock.json

@@ -5198,6 +5198,11 @@
         "domelementtype": "1"
       }
     },
+    "dompurify": {
+      "version": "2.2.8",
+      "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-2.2.8.tgz",
+      "integrity": "sha512-9H0UL59EkDLgY3dUFjLV6IEUaHm5qp3mxSqWw7Yyx4Zhk2Jn2cmLe+CNPP3xy13zl8Bqg+0NehQzkdMoVhGRww=="
+    },
     "domutils": {
       "version": "1.7.0",
       "resolved": "https://registry.npmjs.org/domutils/-/domutils-1.7.0.tgz",

+ 1 - 0
frontend/package.json

@@ -45,6 +45,7 @@
     "@babel/runtime": "^7.13.10",
     "config": "^3.3.6",
     "date-fns": "^2.19.0",
+    "dompurify": "^2.2.8",
     "eslint-config-airbnb-base": "^13.2.0",
     "html-webpack-plugin": "^5.3.1",
     "marked": "^2.0.3",

+ 21 - 2
frontend/src/components/modals/EditNews.vue

@@ -14,7 +14,7 @@
 					<div
 						class="news-item"
 						id="preview"
-						v-html="marked(markdown)"
+						v-html="sanitize(marked(markdown))"
 					></div>
 				</div>
 			</div>
@@ -60,6 +60,7 @@
 <script>
 import { mapActions, mapGetters, mapState } from "vuex";
 import marked from "marked";
+import { sanitize } from "dompurify";
 import Toast from "toasters";
 import { formatDistance } from "date-fns";
 
@@ -120,11 +121,15 @@ export default {
 	},
 	methods: {
 		marked,
+		sanitize,
 		getTitle() {
 			let title = "";
 			const preview = document.getElementById("preview");
 
 			// validate existence of h1 for the page title
+
+			if (preview.childNodes.length === 0) return "";
+
 			if (preview.childNodes[0].tagName !== "H1") {
 				for (
 					let node = 0;
@@ -143,6 +148,9 @@ export default {
 			return title;
 		},
 		create(close) {
+			if (this.markdown === "")
+				return new Toast("News item cannot be empty.");
+
 			const title = this.getTitle();
 			if (!title)
 				return new Toast(
@@ -164,6 +172,9 @@ export default {
 			);
 		},
 		update(close) {
+			if (this.markdown === "")
+				return new Toast("News item cannot be empty.");
+
 			const title = this.getTitle();
 			if (!title)
 				return new Toast(
@@ -212,6 +223,13 @@ export default {
 </style>
 
 <style lang="scss" scoped>
+.night-mode {
+	#markdown-editor-and-preview textarea,
+	#markdown-editor-and-preview #preview {
+		border-color: #fff;
+	}
+}
+
 #markdown-editor-and-preview {
 	display: flex;
 	flex-wrap: wrap;
@@ -235,12 +253,13 @@ export default {
 	#preview {
 		word-break: break-all;
 		overflow: auto;
+		box-shadow: none;
 	}
 
 	textarea,
 	#preview {
 		padding: 5px;
-		border: 1px solid var(--light-grey-3);
+		border: 1px solid var(--light-grey-3) !important;
 		border-radius: 3px;
 		height: 700px;
 	}

+ 3 - 1
frontend/src/components/modals/WhatIsNew.vue

@@ -4,7 +4,7 @@
 			<div slot="body">
 				<div
 					class="section news-item"
-					v-html="marked(news.markdown)"
+					v-html="sanitize(marked(news.markdown))"
 				></div>
 			</div>
 			<div slot="footer">
@@ -29,6 +29,7 @@
 <script>
 import { formatDistance } from "date-fns";
 import marked from "marked";
+import { sanitize } from "dompurify";
 import { mapGetters, mapActions } from "vuex";
 
 import UserIdToUsername from "@/components/UserIdToUsername.vue";
@@ -88,6 +89,7 @@ export default {
 	},
 	methods: {
 		marked,
+		sanitize,
 		formatDistance,
 		...mapActions("modalVisibility", ["openModal"])
 	}

+ 13 - 6
frontend/src/pages/News.vue

@@ -10,7 +10,7 @@
 					:key="item._id"
 					class="section news-item"
 				>
-					<div v-html="marked(item.markdown)"></div>
+					<div v-html="sanitize(marked(item.markdown))"></div>
 					<div class="info">
 						<hr />
 						By
@@ -19,7 +19,14 @@
 							:alt="item.createdBy"
 							:link="true"
 						/>
-						@ {{ formatDate(item.createdAt) }}
+
+						<span :title="new Date(item.createdAt)">
+							{{
+								formatDistance(item.createdAt, new Date(), {
+									addSuffix: true
+								})
+							}}
+						</span>
 					</div>
 				</div>
 				<h3 v-if="news.length === 0" class="has-text-centered">
@@ -32,9 +39,10 @@
 </template>
 
 <script>
-import { format } from "date-fns";
+import { formatDistance } from "date-fns";
 import { mapGetters } from "vuex";
 import marked from "marked";
+import { sanitize } from "dompurify";
 
 import MainHeader from "@/components/layout/MainHeader.vue";
 import MainFooter from "@/components/layout/MainFooter.vue";
@@ -81,9 +89,8 @@ export default {
 	},
 	methods: {
 		marked,
-		formatDate: unix => {
-			return format(unix, "HH:ii:ss dd-MM-yyyy");
-		}
+		sanitize,
+		formatDistance
 	}
 };
 </script>