From 7b0009d31c77d9cf9120e5e674d3105461c4a3ac Mon Sep 17 00:00:00 2001 From: Cameron Wardzala Date: Fri, 6 Jan 2023 14:18:22 -0500 Subject: [PATCH 1/3] Add new SafeInnerHTML component - Add WYSIWYG Editor to Forms page - update WYSIWYG Editor to sanitize HTML --- package.json | 2 + src/components/SafeInnerHTML.jsx | 23 ++ src/components/form/WysiwygEditor.jsx | 48 ++-- src/pages/Components/FormsPage.jsx | 13 ++ src/scss/components/_forms.scss | 20 ++ yarn.lock | 309 +++++++++++++++++++++++++- 6 files changed, 394 insertions(+), 21 deletions(-) create mode 100644 src/components/SafeInnerHTML.jsx diff --git a/package.json b/package.json index a488711..8b6d001 100644 --- a/package.json +++ b/package.json @@ -12,11 +12,13 @@ "color": "^4.2.3", "formik": "^2.2.9", "highlight.js": "^11.5.0", + "isomorphic-dompurify": "^0.25.0", "prop-types": "^15.8.1", "react": "^17.0.2", "react-app-polyfill": "^3.0.0", "react-dom": "^17.0.2", "react-icons": "^4.3.1", + "react-quill": "^2.0.0", "react-router-dom": "^6.3.0", "react-scripts": "5.0.0", "react-select": "^5.2.2", diff --git a/src/components/SafeInnerHTML.jsx b/src/components/SafeInnerHTML.jsx new file mode 100644 index 0000000..256fce0 --- /dev/null +++ b/src/components/SafeInnerHTML.jsx @@ -0,0 +1,23 @@ +import React from 'react'; +import DOMPurify from 'isomorphic-dompurify'; +import PropTypes from 'prop-types'; + +export const purifySettings = { + KEEP_CONTENT: false, + FORBID_TAGS: ['style', 'img'], // In most cases we don't need style or img tags + FORBID_ATTR: ['style'] // In most cases we don't want inline css +}; + +const SafeInnerHTML = ({ as = 'div', html = '' }) => { + const Comp = as; + if (!html) return null; + + return ; +}; + +SafeInnerHTML.propTypes = { + as: PropTypes.elementType, + html: PropTypes.string +}; + +export default SafeInnerHTML; diff --git a/src/components/form/WysiwygEditor.jsx b/src/components/form/WysiwygEditor.jsx index b86d08c..579b2de 100644 --- a/src/components/form/WysiwygEditor.jsx +++ b/src/components/form/WysiwygEditor.jsx @@ -2,6 +2,10 @@ import React from 'react'; import classnames from 'classnames'; import ReactQuill from 'react-quill'; +import SafeInnerHTML, { purifySettings } from '../SafeInnerHTML'; +import DOMPurify from 'isomorphic-dompurify'; + +import 'react-quill/dist/quill.snow.css'; const WysiwygEditor = ({ id = 'wysiwyg1', @@ -14,6 +18,7 @@ const WysiwygEditor = ({ required = false, showRequired = false, className = null, + hideLabel = false, errors = null, touched = null, @@ -33,48 +38,55 @@ const WysiwygEditor = ({ let isRequired = required || showRequired; const labelClassName = classnames({ - 'required': isRequired + required: isRequired }); + const cleanHTML = DOMPurify.sanitize(html, purifySettings); + return (
-
- - {labelHelp} -
- {readOnly &&
} - {!readOnly && + {!!label && !hideLabel && ( +
+ + {labelHelp} +
+ )} + {readOnly && } + {!readOnly && ( - } + )} - {help && - {help} - } + {help && {help}} - {!!errors && touched && + {!!errors && touched && (
{errors}
- } + )}
); -} +}; export default WysiwygEditor; diff --git a/src/pages/Components/FormsPage.jsx b/src/pages/Components/FormsPage.jsx index 01297e9..8cc3836 100644 --- a/src/pages/Components/FormsPage.jsx +++ b/src/pages/Components/FormsPage.jsx @@ -12,6 +12,7 @@ import DisplayFormikState from '../../components/DisplayFormikState'; import { BreadcrumbItem, Breadcrumbs } from '../../components/Breadcrumbs'; import { Link } from 'react-router-dom'; import ReactSelect from '../../components/form/ReactSelect'; +import WysiwygEditor from '../../components/form/WysiwygEditor'; const FormsPage = () => { let fruits = ['apple', 'banana', 'orange', 'avocado']; @@ -136,6 +137,18 @@ const FormsPage = () => {