diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 25160ee7013c..eac68eae1d6c 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -43,6 +43,7 @@ "cors": "^2.8.3", "cross-env": "5.2.0", "css-loader": "4.3.0", + "dompurify": "^3.0.5", "dotenv": "6.2.0", "express": "4.17.3", "express-handlebars": "6.0.5", @@ -117,6 +118,7 @@ "@ffmpeg-installer/ffmpeg": "^1.1.0", "@types/classnames": "^2.3.1", "@types/color": "^3.0.3", + "@types/dompurify": "^3.0.2", "@types/react-router": "^5.1.19", "@types/react-router-dom": "^4.3.1", "@types/react-select": "^2.0.3", @@ -3554,6 +3556,15 @@ "@types/ms": "*" } }, + "node_modules/@types/dompurify": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/dompurify/-/dompurify-3.0.2.tgz", + "integrity": "sha512-YBL4ziFebbbfQfH5mlC+QTJsvh0oJUrWbmxKMyEdL7emlHJqGR2Qb34TEFKj+VCayBvjKy3xczMFNhugThUsfQ==", + "dev": true, + "dependencies": { + "@types/trusted-types": "*" + } + }, "node_modules/@types/error-stack-parser": { "version": "1.3.18", "resolved": "https://registry.npmjs.org/@types/error-stack-parser/-/error-stack-parser-1.3.18.tgz", @@ -3780,6 +3791,12 @@ "resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.2.tgz", "integrity": "sha512-hppQEBDmlwhFAXKJX2KnWLYu5yMfi91yazPb2l+lbJiwW+wdo1gNeRA+3RgNSO39WYX2euey41KEwnqesU2Jew==" }, + "node_modules/@types/trusted-types": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.3.tgz", + "integrity": "sha512-NfQ4gyz38SL8sDNrSixxU2Os1a5xcdFxipAFxYEuLUlvU2uDwS4NUpsImcf1//SlWItCVMMLiylsxbmNMToV/g==", + "dev": true + }, "node_modules/@types/unist": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.6.tgz", @@ -7324,6 +7341,11 @@ "url": "https://github.com/fb55/domhandler?sponsor=1" } }, + "node_modules/dompurify": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.0.5.tgz", + "integrity": "sha512-F9e6wPGtY+8KNMRAVfxeCOHU0/NPWMSENNq4pQctuXRqqdEPW7q3CrLbR5Nse044WwacyjHGOMlvNsBe1y6z9A==" + }, "node_modules/domutils": { "version": "2.8.0", "resolved": "https://registry.npmjs.org/domutils/-/domutils-2.8.0.tgz", @@ -22467,6 +22489,15 @@ "@types/ms": "*" } }, + "@types/dompurify": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/dompurify/-/dompurify-3.0.2.tgz", + "integrity": "sha512-YBL4ziFebbbfQfH5mlC+QTJsvh0oJUrWbmxKMyEdL7emlHJqGR2Qb34TEFKj+VCayBvjKy3xczMFNhugThUsfQ==", + "dev": true, + "requires": { + "@types/trusted-types": "*" + } + }, "@types/error-stack-parser": { "version": "1.3.18", "resolved": "https://registry.npmjs.org/@types/error-stack-parser/-/error-stack-parser-1.3.18.tgz", @@ -22697,6 +22728,12 @@ "resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.2.tgz", "integrity": "sha512-hppQEBDmlwhFAXKJX2KnWLYu5yMfi91yazPb2l+lbJiwW+wdo1gNeRA+3RgNSO39WYX2euey41KEwnqesU2Jew==" }, + "@types/trusted-types": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.3.tgz", + "integrity": "sha512-NfQ4gyz38SL8sDNrSixxU2Os1a5xcdFxipAFxYEuLUlvU2uDwS4NUpsImcf1//SlWItCVMMLiylsxbmNMToV/g==", + "dev": true + }, "@types/unist": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.6.tgz", @@ -25321,6 +25358,11 @@ "domelementtype": "^2.2.0" } }, + "dompurify": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.0.5.tgz", + "integrity": "sha512-F9e6wPGtY+8KNMRAVfxeCOHU0/NPWMSENNq4pQctuXRqqdEPW7q3CrLbR5Nse044WwacyjHGOMlvNsBe1y6z9A==" + }, "domutils": { "version": "2.8.0", "resolved": "https://registry.npmjs.org/domutils/-/domutils-2.8.0.tgz", diff --git a/frontend/package.json b/frontend/package.json index e4afd0ff0508..ea8a941ef70c 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -61,6 +61,7 @@ "cors": "^2.8.3", "cross-env": "5.2.0", "css-loader": "4.3.0", + "dompurify": "^3.0.5", "dotenv": "6.2.0", "express": "4.17.3", "express-handlebars": "6.0.5", @@ -135,6 +136,7 @@ "@ffmpeg-installer/ffmpeg": "^1.1.0", "@types/classnames": "^2.3.1", "@types/color": "^3.0.3", + "@types/dompurify": "^3.0.2", "@types/react-router": "^5.1.19", "@types/react-router-dom": "^4.3.1", "@types/react-select": "^2.0.3", diff --git a/frontend/web/components/Toolip.js b/frontend/web/components/Toolip.js deleted file mode 100644 index 86a8f4ea0a3b..000000000000 --- a/frontend/web/components/Toolip.js +++ /dev/null @@ -1,37 +0,0 @@ -const ReactTooltip = require('react-tooltip') - -const Tooltip = class extends React.Component { - static displayName = 'Tooltip' - - id = Utils.GUID() - - render() { - return ( - - {this.props.title ? ( - - {this.props.title} - - ) : ( - - )} - - {`
${this.props.children}
`} -
-
- ) - } -} - -Tooltip.propTypes = { - children: RequiredElement, - place: OptionalString, -} - -export default Tooltip diff --git a/frontend/web/components/Tooltip.tsx b/frontend/web/components/Tooltip.tsx new file mode 100644 index 000000000000..f4a848cb5d0c --- /dev/null +++ b/frontend/web/components/Tooltip.tsx @@ -0,0 +1,67 @@ +import React from 'react' +import { renderToStaticMarkup } from 'react-dom/server' + +import * as DOMPurify from 'dompurify' +import Utils from 'common/utils/utils' + +const ReactTooltip = require('react-tooltip') + +type StyledTooltipProps = { + children: string +} + +type TooltipProps = { + children: string + plainText: boolean + place?: string | undefined + title: JSX.Element // This is actually the Tooltip parent component +} + +const StyledTooltip = ({ children }: StyledTooltipProps) => ( +
+
+ {`${children}`} +
+) + +const tooltipStyler = (plainText: boolean, children: string): string => { + const html = renderToStaticMarkup( + {plainText ? children : '{{html}}'}, + ) + if (plainText) { + return html + } + return html.replace('{{html}}', DOMPurify.sanitize(children.toString())) +} + +const Tooltip = ({ + children, + place, + plainText, + title, +}: TooltipProps): JSX.Element => { + const id = Utils.GUID() + + return ( + + {title ? ( + + {title} + + ) : ( + + )} + + {tooltipStyler(plainText, children)} + + + ) +} + +export default Tooltip diff --git a/frontend/web/components/tags/Tag.tsx b/frontend/web/components/tags/Tag.tsx index feef6fb4a14b..186fd5661483 100644 --- a/frontend/web/components/tags/Tag.tsx +++ b/frontend/web/components/tags/Tag.tsx @@ -47,6 +47,7 @@ const Tag: FC = ({ return ( onClick?.(tag as TTag)} diff --git a/frontend/web/project/project-components.js b/frontend/web/project/project-components.js index 3c815c95870b..a458301d4cab 100644 --- a/frontend/web/project/project-components.js +++ b/frontend/web/project/project-components.js @@ -8,7 +8,7 @@ import Input from 'components/base/forms/Input' import InputGroup from 'components/base/forms/InputGroup' import PanelSearch from 'components/PanelSearch' import AccountStore from 'common/stores/account-store' -import Tooltip from 'components/Toolip' +import Tooltip from 'components/Tooltip' import ProjectProvider from 'common/providers/ProjectProvider' import AccountProvider from 'common/providers/AccountProvider'