Skip to content

Commit d18a1cf

Browse files
Fix: Created a NewPR for implementing XSS protection-5
1 parent 94af0e6 commit d18a1cf

1 file changed

Lines changed: 33 additions & 8 deletions

File tree

src/components/UserProfile/EditableModal/RoleInfoModal.jsx

Lines changed: 33 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import React, { useState , useEffect } from 'react';
2+
import DOMPurify from 'dompurify';
23
import PropTypes from 'prop-types';
34
import { useDispatch, useSelector, connect } from 'react-redux';
45
import { Button, Modal, ModalBody, ModalFooter, ModalHeader, Col, Row} from 'reactstrap';
@@ -22,9 +23,29 @@ const RoleInfoModal = ({ info, auth, roleName}) => {
2223
const [infoContentModal, setInfoContentModal] = useState('');
2324
const dispatch = useDispatch();
2425

26+
// XSS Protection: Sanitize HTML content
27+
const sanitizeHTML = (htmlContent) => {
28+
if (!htmlContent || typeof htmlContent !== 'string') return '';
29+
30+
return DOMPurify.sanitize(htmlContent, {
31+
ALLOWED_TAGS: ['p', 'br', 'strong', 'em', 'u', 'ul', 'ol', 'li', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'a', 'span', 'div'],
32+
ALLOWED_ATTR: ['href', 'class'],
33+
ALLOW_DATA_ATTR: false,
34+
ALLOWED_URI_REGEXP: /^https?:\/\//, // Only allow http/https URLs
35+
});
36+
};
37+
38+
// XSS Protection: Sanitize text content
39+
const sanitizeText = (textContent) => {
40+
if (!textContent || typeof textContent !== 'string') return '';
41+
return DOMPurify.sanitize(textContent, { ALLOWED_TAGS: [], ALLOWED_ATTR: [] });
42+
};
43+
2544
useEffect(() => {
26-
setInfoContentModal(infoContent);
27-
}, [infoContent]);
45+
// Sanitize content when setting initial state
46+
setInfoContentModal(sanitizeHTML(infoContent));
47+
// eslint-disable-next-line react-hooks/exhaustive-deps
48+
}, [infoContent]); // sanitizeHTML is stable, no need to include in deps
2849

2950
const handleSaveSuccess = async () => {
3051
toast.success('✔ The info was saved successfully!', {
@@ -53,7 +74,9 @@ const RoleInfoModal = ({ info, auth, roleName}) => {
5374
};
5475

5576
const handleInputChange = (content) => {
56-
setInfoContentModal(content);
77+
// Sanitize content from rich text editor
78+
const sanitizedContent = sanitizeHTML(content);
79+
setInfoContentModal(sanitizedContent);
5780
};
5881

5982
const handleSave = async (e) => {
@@ -63,14 +86,16 @@ const RoleInfoModal = ({ info, auth, roleName}) => {
6386
e.preventDefault();
6487
}
6588

66-
const updateInfo = {infoContent: infoContentModal};
89+
// Sanitize content before saving
90+
const sanitizedContent = sanitizeHTML(infoContentModal);
91+
const updateInfo = {infoContent: sanitizedContent};
6792
let saveResult;
6893

6994
// If info doesn't exist in database, create new record
7095
if (!info || !info._id) {
7196
const newInfo = {
72-
infoName: roleName || 'UnknownRoleInfo',
73-
infoContent: infoContentModal,
97+
infoName: sanitizeText(roleName) || 'UnknownRoleInfo',
98+
infoContent: sanitizedContent,
7499
visibility: '0'
75100
};
76101
saveResult = await dispatch(addInfoCollection(newInfo));
@@ -79,7 +104,7 @@ const RoleInfoModal = ({ info, auth, roleName}) => {
79104
saveResult = await dispatch(updateInfoCollection(info._id, updateInfo));
80105
}
81106

82-
setInfoContentModal(infoContentModal);
107+
setInfoContentModal(sanitizedContent);
83108

84109
if (saveResult === 200 || saveResult === 201) {
85110
await handleSaveSuccess();
@@ -110,7 +135,7 @@ const RoleInfoModal = ({ info, auth, roleName}) => {
110135
<div
111136
className={`${styles['role-info-content']} ${darkMode ? styles['dark-mode'] : ''}`}
112137
style={{ paddingLeft: '20px' }}
113-
dangerouslySetInnerHTML={{ __html: infoContentModal }}
138+
dangerouslySetInnerHTML={{ __html: sanitizeHTML(infoContentModal) }}
114139
onClick={() => setIsEditing(true)}
115140
/>}
116141
</ModalBody>

0 commit comments

Comments
 (0)