fix(web): secure rich content rendering
This commit is contained in:
+11
-2
@@ -16,6 +16,9 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
For commercial licensing, please contact support@quantumnous.com
|
||||
*/
|
||||
import DOMPurify from 'dompurify'
|
||||
import { useMemo } from 'react'
|
||||
|
||||
import { cn } from '@/lib/utils'
|
||||
|
||||
interface HtmlContentProps {
|
||||
@@ -24,10 +27,16 @@ interface HtmlContentProps {
|
||||
}
|
||||
|
||||
export function HtmlContent(props: HtmlContentProps) {
|
||||
const html = useMemo(() => DOMPurify.sanitize(props.content), [props.content])
|
||||
|
||||
return (
|
||||
<div
|
||||
className={cn('prose prose-neutral dark:prose-invert max-w-none', props.className)}
|
||||
dangerouslySetInnerHTML={{ __html: props.content }}
|
||||
className={cn(
|
||||
'prose prose-neutral dark:prose-invert max-w-none',
|
||||
props.className
|
||||
)}
|
||||
// eslint-disable-next-line react/no-danger -- html is sanitized above
|
||||
dangerouslySetInnerHTML={{ __html: html }}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
+4
-2
@@ -16,18 +16,20 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
For commercial licensing, please contact support@quantumnous.com
|
||||
*/
|
||||
import { isLikelyHtml } from '@/lib/content-format'
|
||||
import { HtmlContent } from '@/components/html-content'
|
||||
import { Markdown } from '@/components/ui/markdown'
|
||||
|
||||
type RichContentMode = 'markdown' | 'html'
|
||||
|
||||
interface RichContentProps {
|
||||
content: string
|
||||
mode?: RichContentMode
|
||||
breaks?: boolean
|
||||
className?: string
|
||||
}
|
||||
|
||||
export function RichContent(props: RichContentProps) {
|
||||
if (isLikelyHtml(props.content)) {
|
||||
if (props.mode === 'html') {
|
||||
return <HtmlContent content={props.content} className={props.className} />
|
||||
}
|
||||
|
||||
|
||||
+5
-2
@@ -19,10 +19,12 @@ For commercial licensing, please contact support@quantumnous.com
|
||||
import { useQuery } from '@tanstack/react-query'
|
||||
import { Construction } from 'lucide-react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
|
||||
import { PublicLayout } from '@/components/layout'
|
||||
import { RichContent } from '@/components/rich-content'
|
||||
import { Skeleton } from '@/components/ui/skeleton'
|
||||
import { PublicLayout } from '@/components/layout'
|
||||
import { isHttpUrl } from '@/lib/content-format'
|
||||
import { isHttpUrl, isLikelyHtml } from '@/lib/content-format'
|
||||
|
||||
import { getAboutContent } from './api'
|
||||
|
||||
function EmptyAboutState() {
|
||||
@@ -159,6 +161,7 @@ export function About() {
|
||||
<PublicLayout>
|
||||
<div className='mx-auto max-w-6xl px-4 py-8'>
|
||||
<RichContent
|
||||
mode={isLikelyHtml(rawContent) ? 'html' : 'markdown'}
|
||||
content={rawContent}
|
||||
className='prose-neutral dark:prose-invert max-w-none'
|
||||
/>
|
||||
|
||||
+10
-3
@@ -17,10 +17,13 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
For commercial licensing, please contact support@quantumnous.com
|
||||
*/
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { useAuthStore } from '@/stores/auth-store'
|
||||
import { RichContent } from '@/components/rich-content'
|
||||
|
||||
import { PublicLayout } from '@/components/layout'
|
||||
import { Footer } from '@/components/layout/components/footer'
|
||||
import { RichContent } from '@/components/rich-content'
|
||||
import { isLikelyHtml } from '@/lib/content-format'
|
||||
import { useAuthStore } from '@/stores/auth-store'
|
||||
|
||||
import { CTA, Features, Hero, HowItWorks, Stats } from './components'
|
||||
import { useHomePageContent } from './hooks'
|
||||
|
||||
@@ -57,7 +60,11 @@ export function Home() {
|
||||
return (
|
||||
<PublicLayout>
|
||||
<div className='mx-auto max-w-6xl px-4 py-8'>
|
||||
<RichContent content={content} className='custom-home-content' />
|
||||
<RichContent
|
||||
mode={isLikelyHtml(content) ? 'html' : 'markdown'}
|
||||
content={content}
|
||||
className='custom-home-content'
|
||||
/>
|
||||
</div>
|
||||
</PublicLayout>
|
||||
)
|
||||
|
||||
+5
-2
@@ -19,12 +19,14 @@ For commercial licensing, please contact support@quantumnous.com
|
||||
import { useQuery } from '@tanstack/react-query'
|
||||
import { FileWarning } from 'lucide-react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
|
||||
import { PublicLayout } from '@/components/layout'
|
||||
import { RichContent } from '@/components/rich-content'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'
|
||||
import { Skeleton } from '@/components/ui/skeleton'
|
||||
import { PublicLayout } from '@/components/layout'
|
||||
import { isHttpUrl } from '@/lib/content-format'
|
||||
import { isHttpUrl, isLikelyHtml } from '@/lib/content-format'
|
||||
|
||||
import type { LegalDocumentResponse } from './types'
|
||||
|
||||
type LegalDocumentProps = {
|
||||
@@ -127,6 +129,7 @@ export function LegalDocument({
|
||||
</div>
|
||||
|
||||
<RichContent
|
||||
mode={isLikelyHtml(rawContent) ? 'html' : 'markdown'}
|
||||
content={rawContent}
|
||||
className='prose-neutral dark:prose-invert max-w-none'
|
||||
/>
|
||||
|
||||
Reference in New Issue
Block a user