diff --git a/Makefile b/Makefile
index e623b095..abaf07e1 100644
--- a/Makefile
+++ b/Makefile
@@ -235,7 +235,7 @@ test-python:
$(PYTEST) fittrackee --cov-config .coveragerc --cov=fittrackee --cov-report term-missing $(PYTEST_ARGS)
test-client:
- cd fittrackee_client && $(NPM) test:unit
+ cd fittrackee_client && $(NPM) test:unit $(MOCHA_ARGS)
type-check:
echo 'Running mypy...'
diff --git a/fittrackee_client/package.json b/fittrackee_client/package.json
index ce767f6b..ee3676c5 100644
--- a/fittrackee_client/package.json
+++ b/fittrackee_client/package.json
@@ -28,8 +28,8 @@
"linkify-html": "^4.0.2",
"linkifyjs": "^4.0.2",
"register-service-worker": "^1.7.1",
+ "sanitize-html": "^2.7.3",
"vue": "^3.2.43",
- "vue-3-sanitize": "^0.1.4",
"vue-chart-3": "3.1.1",
"vue-fullscreen": "^3.1.1",
"vue-i18n": "^9.2.2",
@@ -40,6 +40,7 @@
"@intlify/vue-i18n-loader": "^4.2.0",
"@types/chai": "^4.3.4",
"@types/mocha": "^10.0.0",
+ "@types/sanitize-html": "^2.6.2",
"@typescript-eslint/eslint-plugin": "^5.42.1",
"@typescript-eslint/parser": "^5.42.1",
"@vue/cli-plugin-babel": "~5.0.8",
diff --git a/fittrackee_client/src/components/Workout/WorkoutDetail/WorkoutNotes.vue b/fittrackee_client/src/components/Workout/WorkoutDetail/WorkoutNotes.vue
index dd6079e5..4571eec7 100644
--- a/fittrackee_client/src/components/Workout/WorkoutDetail/WorkoutNotes.vue
+++ b/fittrackee_client/src/components/Workout/WorkoutDetail/WorkoutNotes.vue
@@ -3,31 +3,38 @@
{{ $t('workouts.NOTES') }}
-
+
diff --git a/fittrackee_client/src/main.ts b/fittrackee_client/src/main.ts
index a56f81ed..f7f15d86 100644
--- a/fittrackee_client/src/main.ts
+++ b/fittrackee_client/src/main.ts
@@ -14,7 +14,6 @@ import {
} from 'chart.js'
import ChartDataLabels from 'chartjs-plugin-datalabels'
import { createApp } from 'vue'
-import Vue3Sanitize from "vue-3-sanitize"
import VueFullscreen from 'vue-fullscreen'
import './registerServiceWorker'
@@ -48,7 +47,6 @@ const app = createApp(App)
.use(store)
.use(router)
.use(VueFullscreen, { name: 'VFullscreen' })
- .use(Vue3Sanitize, { allowedTags: ['a'], disallowedTagsMode: 'escape' })
.directive('click-outside', clickOutsideDirective)
customComponents.forEach((component) => {
diff --git a/fittrackee_client/src/utils/inputs.ts b/fittrackee_client/src/utils/inputs.ts
new file mode 100644
index 00000000..7ff44a16
--- /dev/null
+++ b/fittrackee_client/src/utils/inputs.ts
@@ -0,0 +1,9 @@
+import linkifyHtml from 'linkify-html'
+import sanitizeHtml from 'sanitize-html'
+
+export const linkifyAndClean = (input: string): string => {
+ return sanitizeHtml(linkifyHtml(input, { target: '_blank' }), {
+ allowedTags: ['a'],
+ disallowedTagsMode: 'escape',
+ })
+}
diff --git a/fittrackee_client/src/vue-3-sanitize.d.ts b/fittrackee_client/src/vue-3-sanitize.d.ts
deleted file mode 100644
index b509ce2e..00000000
--- a/fittrackee_client/src/vue-3-sanitize.d.ts
+++ /dev/null
@@ -1 +0,0 @@
-declare module 'vue-3-sanitize';
\ No newline at end of file
diff --git a/fittrackee_client/tests/unit/utils/inputs.spec.ts b/fittrackee_client/tests/unit/utils/inputs.spec.ts
new file mode 100644
index 00000000..509915c1
--- /dev/null
+++ b/fittrackee_client/tests/unit/utils/inputs.spec.ts
@@ -0,0 +1,71 @@
+import { assert } from 'chai'
+
+import { linkifyAndClean } from '@/utils/inputs'
+
+describe('linkifyAndClean (clean input remains unchanged)', () => {
+ const testInputs = [
+ 'just a text\nfor "test"',
+ 'link: example',
+ 'link: example',
+ ]
+
+ testInputs.map((testInput) => {
+ it(`it returns unmodified input: '${testInput}'`, () => {
+ assert.equal(linkifyAndClean(testInput), testInput)
+ })
+ })
+})
+
+describe('linkifyAndClean (URL is linkified)', () => {
+ it('it returns URL as link with target blank', () => {
+ assert.equal(
+ linkifyAndClean('link: http://www.example.com'),
+ 'link: http://www.example.com'
+ )
+ })
+})
+
+describe('linkifyAndClean (input sanitization)', () => {
+ const testsParams = [
+ {
+ description: 'it escapes "script" tags',
+ inputString: "",
+ expectedString: "<script>alert('evil!')</script>",
+ },
+ {
+ description: 'it escapes nested tags',
+ inputString: '
test
',
+ expectedString: '<p><b>test</b></p>',
+ },
+ {
+ description: 'it escapes single tag',
+ inputString: 'test',
+ expectedString: '<p>test</p>',
+ },
+ {
+ description: 'it removes css classe',
+ inputString: '
test
',
+ expectedString: '<div>test</div>',
+ },
+ {
+ description: 'it removes style attribute',
+ inputString: 'test
',
+ expectedString: '<div>test</div>',
+ },
+ {
+ description: 'it keeps nested HTML link',
+ inputString: 'example
',
+ expectedString:
+ '<p>example</p>',
+ },
+ ]
+
+ testsParams.map((testParams) => {
+ it(testParams.description, () => {
+ assert.equal(
+ linkifyAndClean(testParams.inputString),
+ testParams.expectedString
+ )
+ })
+ })
+})
diff --git a/fittrackee_client/yarn.lock b/fittrackee_client/yarn.lock
index 93d40144..3e4f0fcd 100644
--- a/fittrackee_client/yarn.lock
+++ b/fittrackee_client/yarn.lock
@@ -1433,6 +1433,13 @@
resolved "https://registry.yarnpkg.com/@types/retry/-/retry-0.12.0.tgz#2b35eccfcee7d38cd72ad99232fbd58bffb3c84d"
integrity sha512-wWKOClTTiizcZhXnPY4wikVAwmdYHp8q6DmC+EJUzAMsycb7HB32Kh9RN4+0gExjmPmZSAQjgURXIGATPegAvA==
+"@types/sanitize-html@^2.6.2":
+ version "2.6.2"
+ resolved "https://registry.yarnpkg.com/@types/sanitize-html/-/sanitize-html-2.6.2.tgz#9c47960841b9def1e4c9dfebaaab010a3f6e97b9"
+ integrity sha512-7Lu2zMQnmHHQGKXVvCOhSziQMpa+R2hMHFefzbYoYMHeaXR0uXqNeOc3JeQQQ8/6Xa2Br/P1IQTLzV09xxAiUQ==
+ dependencies:
+ htmlparser2 "^6.0.0"
+
"@types/semver@^7.3.12":
version "7.3.13"
resolved "https://registry.yarnpkg.com/@types/semver/-/semver-7.3.13.tgz#da4bfd73f49bd541d28920ab0e2bf0ee80f71c91"
@@ -6760,7 +6767,7 @@ safe-regex-test@^1.0.0:
resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a"
integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==
-sanitize-html@^2.0.0:
+sanitize-html@^2.7.3:
version "2.7.3"
resolved "https://registry.yarnpkg.com/sanitize-html/-/sanitize-html-2.7.3.tgz#166c868444ee4f9fd7352ac8c63fa86c343fc2bd"
integrity sha512-jMaHG29ak4miiJ8wgqA1849iInqORgNv7SLfSw9LtfOhEUQ1C0YHKH73R+hgyufBW9ZFeJrb057k9hjlfBCVlw==
@@ -7681,13 +7688,6 @@ vary@~1.1.2:
resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc"
integrity sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==
-vue-3-sanitize@^0.1.4:
- version "0.1.4"
- resolved "https://registry.yarnpkg.com/vue-3-sanitize/-/vue-3-sanitize-0.1.4.tgz#1bb23279e9dd6e39e2c9d992ebb366da3e6f49a2"
- integrity sha512-8+iU+MC94ibyOBUpn4g2hk8Dy32M7PKIi3UoUG+6Pd47J/uHIDdOdeXEEmCM/rVdyFqNXqLfO+yZT2BesGNlXA==
- dependencies:
- sanitize-html "^2.0.0"
-
vue-chart-3@3.1.1:
version "3.1.1"
resolved "https://registry.yarnpkg.com/vue-chart-3/-/vue-chart-3-3.1.1.tgz#7498c580444a92513bbea4f5df7803d295c20c9b"