diff --git a/package.json b/package.json index 33a98aab..c77413b3 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "react-helsinki-headless-cms", - "version": "1.0.0-alpha296", + "version": "1.0.0-alpha298", "description": "React components for displaying Headless CMS content according to guidelines set by HDS", "main": "cjs/index.js", "module": "index.js", diff --git a/src/common/components/htmlToReact/HtmlToReact.tsx b/src/common/components/htmlToReact/HtmlToReact.tsx index 71a9cb5e..09229225 100644 --- a/src/common/components/htmlToReact/HtmlToReact.tsx +++ b/src/common/components/htmlToReact/HtmlToReact.tsx @@ -11,12 +11,13 @@ import parse, { import classNames from 'classnames'; import styles from './htmlToReact.module.scss'; -import { isTrustedOrigin, validateTrustedOriginsFormat } from './utils'; import { DefaultH2, DefaultP, IframeForEmbeddedMedia, } from './replaceComponents'; +import validateTrustedOriginsFormat from '../../../core/utils/validateTrustedOriginsFormat'; +import isTrustedOrigin from '../../../core/utils/isTrustedOrigin'; const hasClassName = (domNode: Element, className: string) => domNode.attribs.class?.split(/\s+/)?.includes(className) ?? false; diff --git a/src/common/components/htmlToReact/utils/index.ts b/src/common/components/htmlToReact/utils/index.ts deleted file mode 100644 index a4a304b2..00000000 --- a/src/common/components/htmlToReact/utils/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export { default as isTrustedOrigin } from './isTrustedOrigin'; -export { default as validateTrustedOriginsFormat } from './validateTrustedOriginsFormat'; diff --git a/src/core/configProvider/configContext.ts b/src/core/configProvider/configContext.ts index b0f75ce5..20eefa2f 100644 --- a/src/core/configProvider/configContext.ts +++ b/src/core/configProvider/configContext.ts @@ -87,7 +87,6 @@ export type Config = { htmlSanitizer: { allowedUnsafeTags: HtmlToReactProps['allowedUnsafeTags']; trustedOrigins: HtmlToReactProps['trustedOrigins']; - allowUnsafeSocialMediaScript: boolean; }; }; diff --git a/src/core/configProvider/defaultConfig.tsx b/src/core/configProvider/defaultConfig.tsx index 6bde19de..e5995ba7 100644 --- a/src/core/configProvider/defaultConfig.tsx +++ b/src/core/configProvider/defaultConfig.tsx @@ -98,6 +98,5 @@ export const defaultConfig: Config = { htmlSanitizer: { allowedUnsafeTags: [], trustedOrigins: [], - allowUnsafeSocialMediaScript: false, }, }; diff --git a/src/core/pageModules/SocialMediaFeedModule/SocialMediaFeedModule.stories.tsx b/src/core/pageModules/SocialMediaFeedModule/SocialMediaFeedModule.stories.tsx index 06c958eb..29b5da53 100644 --- a/src/core/pageModules/SocialMediaFeedModule/SocialMediaFeedModule.stories.tsx +++ b/src/core/pageModules/SocialMediaFeedModule/SocialMediaFeedModule.stories.tsx @@ -25,8 +25,7 @@ const Template: StoryFn = (args) => ( ...defaultConfig, htmlSanitizer: { allowedUnsafeTags: [], - trustedOrigins: [], - allowUnsafeSocialMediaScript: true, + trustedOrigins: ['https://plugins.flockler.com'], }, }} > @@ -36,6 +35,6 @@ const Template: StoryFn = (args) => ( ); -export const ImageGalleryModuleDefault = { +export const SocialMediaFeedModuleDefault = { render: Template, }; diff --git a/src/core/pageModules/SocialMediaFeedModule/SocialMediaFeedModule.tsx b/src/core/pageModules/SocialMediaFeedModule/SocialMediaFeedModule.tsx index 40b81772..4f72beb9 100644 --- a/src/core/pageModules/SocialMediaFeedModule/SocialMediaFeedModule.tsx +++ b/src/core/pageModules/SocialMediaFeedModule/SocialMediaFeedModule.tsx @@ -1,8 +1,11 @@ -import React, { useLayoutEffect, useRef } from 'react'; +import React, { useLayoutEffect, useMemo, useRef } from 'react'; +import DOMPurify from 'isomorphic-dompurify'; +import parse, { DOMNode } from 'html-react-parser'; import Text from '../../../common/components/text/Text'; import { useConfig } from '../../configProvider/useConfig'; import styles from '../pageModules.module.scss'; +import isTrustedOrigin from '../../utils/isTrustedOrigin'; export interface SocialMediaFeedModuleProps { anchor: string; @@ -16,30 +19,52 @@ export function SocialMediaFeedModule({ script, }: SocialMediaFeedModuleProps) { const { - htmlSanitizer: { allowUnsafeSocialMediaScript }, + htmlSanitizer: { trustedOrigins }, } = useConfig(); const scriptWrapperRef = useRef(); useLayoutEffect(() => { - if (allowUnsafeSocialMediaScript) { + if (scriptWrapperRef.current?.innerHTML) { const range = document.createRange(); range.selectNode(scriptWrapperRef.current); - const documentFragment = range.createContextualFragment(script); + const documentFragment = range.createContextualFragment( + scriptWrapperRef.current.innerHTML, + ); scriptWrapperRef.current.innerHTML = ''; scriptWrapperRef.current.append(documentFragment); } - }, [script, allowUnsafeSocialMediaScript]); + }, [scriptWrapperRef.current?.innerHTML]); - if (!script) return null; + const sanitizeScripts = (domNode: DOMNode) => { + if ('attribs' in domNode) { + if ( + domNode.name === 'script' && + !isTrustedOrigin(domNode.attribs.src, trustedOrigins) + ) { + // eslint-disable-next-line no-console + console.warn( + 'The unsafe social media feed script is not allowed. Please enable it in hcrc library configs.', + ); + return
; + } + } + + return domNode; + }; + + const clean = useMemo( + () => + DOMPurify.sanitize(script, { + FORCE_BODY: true, + ADD_TAGS: ['script', 'div'], + }), + [script], + ); - if (!allowUnsafeSocialMediaScript) { - // eslint-disable-next-line no-console - console.warn( - 'The unsafe social media feed script is not allowed. Please enable it in hcrc library configs.', - ); - return null; - } + const htmlReactParserOptions = { + replace: (domNode) => sanitizeScripts(domNode), + }; return (
@@ -48,7 +73,7 @@ export function SocialMediaFeedModule({ {title} )} -
+
{parse(clean, htmlReactParserOptions)}
); } diff --git a/src/common/components/htmlToReact/utils/isTrustedOrigin.ts b/src/core/utils/isTrustedOrigin.ts similarity index 100% rename from src/common/components/htmlToReact/utils/isTrustedOrigin.ts rename to src/core/utils/isTrustedOrigin.ts diff --git a/src/common/components/htmlToReact/utils/validateTrustedOriginsFormat.ts b/src/core/utils/validateTrustedOriginsFormat.ts similarity index 100% rename from src/common/components/htmlToReact/utils/validateTrustedOriginsFormat.ts rename to src/core/utils/validateTrustedOriginsFormat.ts