feat: add HTML sanitization for notification messages
Implement sanitizeHtmlWithStyles utility to safely render HTML content in notification messages while removing dangerous tags and attributes. Update NotificationDetailsCard to use dangerouslySetInnerHTML with sanitization for message display.
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
"use client";
|
||||
import { TNotificationDetailsResponse } from "@/lib/api/types/NotificationHistory";
|
||||
import { capitalize, unixToDate } from "@/utils/shared";
|
||||
import { sanitizeHtmlWithStyles } from "@/lib/utils";
|
||||
import { FC } from "react";
|
||||
import IsVisible from "./IsVisible";
|
||||
import Status from "./Status";
|
||||
@@ -43,7 +44,12 @@ export const NotificationDetailsCard: FC<IProps> = ({ data }) => {
|
||||
<IsVisible condition={!!data.message}>
|
||||
<hr className="my-5" />
|
||||
<DetailItem title="Message">
|
||||
<p className="text-base leading-relaxed">{data.message}</p>
|
||||
<div
|
||||
className="text-base leading-relaxed"
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: sanitizeHtmlWithStyles(data.message || '')
|
||||
}}
|
||||
/>
|
||||
</DetailItem>
|
||||
</IsVisible>
|
||||
</div>
|
||||
|
||||
+78
-3
@@ -1,6 +1,81 @@
|
||||
import { clsx, type ClassValue } from "clsx"
|
||||
import { twMerge } from "tailwind-merge"
|
||||
import { clsx, type ClassValue } from "clsx";
|
||||
import { twMerge } from "tailwind-merge";
|
||||
|
||||
export function cn(...inputs: ClassValue[]) {
|
||||
return twMerge(clsx(inputs))
|
||||
return twMerge(clsx(inputs));
|
||||
}
|
||||
|
||||
export function sanitizeHtmlWithStyles(htmlString: string): string {
|
||||
if (!htmlString) return "";
|
||||
|
||||
if (typeof window !== "undefined") {
|
||||
const tempDiv = document.createElement("div");
|
||||
tempDiv.innerHTML = htmlString;
|
||||
|
||||
const dangerousTags = [
|
||||
"script",
|
||||
"iframe",
|
||||
"object",
|
||||
"embed",
|
||||
"form",
|
||||
"input",
|
||||
"button",
|
||||
];
|
||||
const dangerousAttributes = [
|
||||
"onclick",
|
||||
"onload",
|
||||
"onerror",
|
||||
"onmouseover",
|
||||
"onfocus",
|
||||
"onblur",
|
||||
];
|
||||
|
||||
dangerousTags.forEach((tag) => {
|
||||
const elements = tempDiv.getElementsByTagName(tag);
|
||||
for (let i = elements.length - 1; i >= 0; i--) {
|
||||
elements[i].remove();
|
||||
}
|
||||
});
|
||||
|
||||
const allElements = tempDiv.getElementsByTagName("*");
|
||||
for (let i = 0; i < allElements.length; i++) {
|
||||
const element = allElements[i];
|
||||
dangerousAttributes.forEach((attr) => {
|
||||
if (element.hasAttribute(attr)) {
|
||||
element.removeAttribute(attr);
|
||||
}
|
||||
});
|
||||
|
||||
Array.from(element.attributes).forEach((attr) => {
|
||||
if (attr.name.toLowerCase().startsWith("on")) {
|
||||
element.removeAttribute(attr.name);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return tempDiv.innerHTML;
|
||||
}
|
||||
|
||||
return htmlString
|
||||
.replace(/<script[^>]*>[\s\S]*?<\/script>/gi, "")
|
||||
.replace(/<iframe[^>]*>[\s\S]*?<\/iframe>/gi, "")
|
||||
.replace(/<object[^>]*>[\s\S]*?<\/object>/gi, "")
|
||||
.replace(/<embed[^>]*>/gi, "")
|
||||
.replace(/on\w+="[^"]*"/gi, "")
|
||||
.replace(/on\w+='[^']*'/gi, "");
|
||||
}
|
||||
|
||||
export function stripHtmlTags(htmlString: string): string {
|
||||
if (!htmlString) return "";
|
||||
|
||||
if (typeof window !== "undefined") {
|
||||
const tempDiv = document.createElement("div");
|
||||
tempDiv.innerHTML = htmlString;
|
||||
return tempDiv.textContent || tempDiv.innerText || "";
|
||||
}
|
||||
|
||||
return htmlString
|
||||
.replace(/<[^>]*>/g, "")
|
||||
.replace(/ /g, " ")
|
||||
.trim();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user