Client - move input sanitization in utils
This commit is contained in:
parent
13b944e26f
commit
eb57935b58
2
Makefile
2
Makefile
@ -235,7 +235,7 @@ test-python:
|
|||||||
$(PYTEST) fittrackee --cov-config .coveragerc --cov=fittrackee --cov-report term-missing $(PYTEST_ARGS)
|
$(PYTEST) fittrackee --cov-config .coveragerc --cov=fittrackee --cov-report term-missing $(PYTEST_ARGS)
|
||||||
|
|
||||||
test-client:
|
test-client:
|
||||||
cd fittrackee_client && $(NPM) test:unit
|
cd fittrackee_client && $(NPM) test:unit $(MOCHA_ARGS)
|
||||||
|
|
||||||
type-check:
|
type-check:
|
||||||
echo 'Running mypy...'
|
echo 'Running mypy...'
|
||||||
|
@ -28,8 +28,8 @@
|
|||||||
"linkify-html": "^4.0.2",
|
"linkify-html": "^4.0.2",
|
||||||
"linkifyjs": "^4.0.2",
|
"linkifyjs": "^4.0.2",
|
||||||
"register-service-worker": "^1.7.1",
|
"register-service-worker": "^1.7.1",
|
||||||
|
"sanitize-html": "^2.7.3",
|
||||||
"vue": "^3.2.43",
|
"vue": "^3.2.43",
|
||||||
"vue-3-sanitize": "^0.1.4",
|
|
||||||
"vue-chart-3": "3.1.1",
|
"vue-chart-3": "3.1.1",
|
||||||
"vue-fullscreen": "^3.1.1",
|
"vue-fullscreen": "^3.1.1",
|
||||||
"vue-i18n": "^9.2.2",
|
"vue-i18n": "^9.2.2",
|
||||||
@ -40,6 +40,7 @@
|
|||||||
"@intlify/vue-i18n-loader": "^4.2.0",
|
"@intlify/vue-i18n-loader": "^4.2.0",
|
||||||
"@types/chai": "^4.3.4",
|
"@types/chai": "^4.3.4",
|
||||||
"@types/mocha": "^10.0.0",
|
"@types/mocha": "^10.0.0",
|
||||||
|
"@types/sanitize-html": "^2.6.2",
|
||||||
"@typescript-eslint/eslint-plugin": "^5.42.1",
|
"@typescript-eslint/eslint-plugin": "^5.42.1",
|
||||||
"@typescript-eslint/parser": "^5.42.1",
|
"@typescript-eslint/parser": "^5.42.1",
|
||||||
"@vue/cli-plugin-babel": "~5.0.8",
|
"@vue/cli-plugin-babel": "~5.0.8",
|
||||||
|
@ -3,31 +3,38 @@
|
|||||||
<Card>
|
<Card>
|
||||||
<template #title>{{ $t('workouts.NOTES') }}</template>
|
<template #title>{{ $t('workouts.NOTES') }}</template>
|
||||||
<template #content>
|
<template #content>
|
||||||
<span v-html="notes !== '' ? $sanitize(linkifyHtml(notes, { target: '_blank' })) : $t('workouts.NO_NOTES')" />
|
<span
|
||||||
|
v-html="
|
||||||
|
notes && notes !== ''
|
||||||
|
? linkifyAndClean(notes)
|
||||||
|
: $t('workouts.NO_NOTES')
|
||||||
|
"
|
||||||
|
/>
|
||||||
</template>
|
</template>
|
||||||
</Card>
|
</Card>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import linkifyHtml from 'linkify-html'
|
import { toRefs, withDefaults } from 'vue'
|
||||||
import { toRefs, withDefaults } from 'vue'
|
|
||||||
|
|
||||||
interface Props {
|
import { linkifyAndClean } from '@/utils/inputs'
|
||||||
notes?: string | null
|
|
||||||
}
|
|
||||||
const props = withDefaults(defineProps<Props>(), {
|
|
||||||
notes: () => null,
|
|
||||||
})
|
|
||||||
|
|
||||||
const { notes } = toRefs(props)
|
interface Props {
|
||||||
|
notes?: string | null
|
||||||
|
}
|
||||||
|
const props = withDefaults(defineProps<Props>(), {
|
||||||
|
notes: () => null,
|
||||||
|
})
|
||||||
|
|
||||||
|
const { notes } = toRefs(props)
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
#workout-note {
|
#workout-note {
|
||||||
::v-deep(.card-content) {
|
::v-deep(.card-content) {
|
||||||
font-style: italic;
|
font-style: italic;
|
||||||
white-space: pre-wrap;
|
white-space: pre-wrap;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
</style>
|
</style>
|
||||||
|
@ -14,7 +14,6 @@ import {
|
|||||||
} from 'chart.js'
|
} from 'chart.js'
|
||||||
import ChartDataLabels from 'chartjs-plugin-datalabels'
|
import ChartDataLabels from 'chartjs-plugin-datalabels'
|
||||||
import { createApp } from 'vue'
|
import { createApp } from 'vue'
|
||||||
import Vue3Sanitize from "vue-3-sanitize"
|
|
||||||
import VueFullscreen from 'vue-fullscreen'
|
import VueFullscreen from 'vue-fullscreen'
|
||||||
|
|
||||||
import './registerServiceWorker'
|
import './registerServiceWorker'
|
||||||
@ -48,7 +47,6 @@ const app = createApp(App)
|
|||||||
.use(store)
|
.use(store)
|
||||||
.use(router)
|
.use(router)
|
||||||
.use(VueFullscreen, { name: 'VFullscreen' })
|
.use(VueFullscreen, { name: 'VFullscreen' })
|
||||||
.use(Vue3Sanitize, { allowedTags: ['a'], disallowedTagsMode: 'escape' })
|
|
||||||
.directive('click-outside', clickOutsideDirective)
|
.directive('click-outside', clickOutsideDirective)
|
||||||
|
|
||||||
customComponents.forEach((component) => {
|
customComponents.forEach((component) => {
|
||||||
|
9
fittrackee_client/src/utils/inputs.ts
Normal file
9
fittrackee_client/src/utils/inputs.ts
Normal file
@ -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',
|
||||||
|
})
|
||||||
|
}
|
1
fittrackee_client/src/vue-3-sanitize.d.ts
vendored
1
fittrackee_client/src/vue-3-sanitize.d.ts
vendored
@ -1 +0,0 @@
|
|||||||
declare module 'vue-3-sanitize';
|
|
71
fittrackee_client/tests/unit/utils/inputs.spec.ts
Normal file
71
fittrackee_client/tests/unit/utils/inputs.spec.ts
Normal file
@ -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: <a href="http://www.example.com">example</a>',
|
||||||
|
'link: <a href="http://www.example.com" target="_blank">example</a>',
|
||||||
|
]
|
||||||
|
|
||||||
|
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: <a href="http://www.example.com" target="_blank">http://www.example.com</a>'
|
||||||
|
)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('linkifyAndClean (input sanitization)', () => {
|
||||||
|
const testsParams = [
|
||||||
|
{
|
||||||
|
description: 'it escapes "script" tags',
|
||||||
|
inputString: "<script>alert('evil!')</script>",
|
||||||
|
expectedString: "<script>alert('evil!')</script>",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: 'it escapes nested tags',
|
||||||
|
inputString: '<p><b>test</b></p>',
|
||||||
|
expectedString: '<p><b>test</b></p>',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: 'it escapes single tag',
|
||||||
|
inputString: '<p>test',
|
||||||
|
expectedString: '<p>test</p>',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: 'it removes css classe',
|
||||||
|
inputString: '<div class="active">test</div>',
|
||||||
|
expectedString: '<div>test</div>',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: 'it removes style attribute',
|
||||||
|
inputString: '<div style="display:none;">test</div>',
|
||||||
|
expectedString: '<div>test</div>',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: 'it keeps nested HTML link',
|
||||||
|
inputString: '<p><a href="http://www.example.com">example</a></p>',
|
||||||
|
expectedString:
|
||||||
|
'<p><a href="http://www.example.com">example</a></p>',
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
testsParams.map((testParams) => {
|
||||||
|
it(testParams.description, () => {
|
||||||
|
assert.equal(
|
||||||
|
linkifyAndClean(testParams.inputString),
|
||||||
|
testParams.expectedString
|
||||||
|
)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
@ -1433,6 +1433,13 @@
|
|||||||
resolved "https://registry.yarnpkg.com/@types/retry/-/retry-0.12.0.tgz#2b35eccfcee7d38cd72ad99232fbd58bffb3c84d"
|
resolved "https://registry.yarnpkg.com/@types/retry/-/retry-0.12.0.tgz#2b35eccfcee7d38cd72ad99232fbd58bffb3c84d"
|
||||||
integrity sha512-wWKOClTTiizcZhXnPY4wikVAwmdYHp8q6DmC+EJUzAMsycb7HB32Kh9RN4+0gExjmPmZSAQjgURXIGATPegAvA==
|
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":
|
"@types/semver@^7.3.12":
|
||||||
version "7.3.13"
|
version "7.3.13"
|
||||||
resolved "https://registry.yarnpkg.com/@types/semver/-/semver-7.3.13.tgz#da4bfd73f49bd541d28920ab0e2bf0ee80f71c91"
|
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"
|
resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a"
|
||||||
integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==
|
integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==
|
||||||
|
|
||||||
sanitize-html@^2.0.0:
|
sanitize-html@^2.7.3:
|
||||||
version "2.7.3"
|
version "2.7.3"
|
||||||
resolved "https://registry.yarnpkg.com/sanitize-html/-/sanitize-html-2.7.3.tgz#166c868444ee4f9fd7352ac8c63fa86c343fc2bd"
|
resolved "https://registry.yarnpkg.com/sanitize-html/-/sanitize-html-2.7.3.tgz#166c868444ee4f9fd7352ac8c63fa86c343fc2bd"
|
||||||
integrity sha512-jMaHG29ak4miiJ8wgqA1849iInqORgNv7SLfSw9LtfOhEUQ1C0YHKH73R+hgyufBW9ZFeJrb057k9hjlfBCVlw==
|
integrity sha512-jMaHG29ak4miiJ8wgqA1849iInqORgNv7SLfSw9LtfOhEUQ1C0YHKH73R+hgyufBW9ZFeJrb057k9hjlfBCVlw==
|
||||||
@ -7681,13 +7688,6 @@ vary@~1.1.2:
|
|||||||
resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc"
|
resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc"
|
||||||
integrity sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==
|
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:
|
vue-chart-3@3.1.1:
|
||||||
version "3.1.1"
|
version "3.1.1"
|
||||||
resolved "https://registry.yarnpkg.com/vue-chart-3/-/vue-chart-3-3.1.1.tgz#7498c580444a92513bbea4f5df7803d295c20c9b"
|
resolved "https://registry.yarnpkg.com/vue-chart-3/-/vue-chart-3-3.1.1.tgz#7498c580444a92513bbea4f5df7803d295c20c9b"
|
||||||
|
Loading…
Reference in New Issue
Block a user