From 8fc922588bb0d03c67d0e1bc4640c5f4c3ba2e59 Mon Sep 17 00:00:00 2001 From: Gabe Kangas Date: Sun, 26 Jun 2022 20:09:07 -0700 Subject: [PATCH] Add noreferrer automatically to link tags. Closes #1941 --- web/components/layouts/Main.tsx | 10 +++++++- web/utils/no-link-referrer.ts | 44 +++++++++++++++++++++++++++++++++ 2 files changed, 53 insertions(+), 1 deletion(-) create mode 100644 web/utils/no-link-referrer.ts diff --git a/web/components/layouts/Main.tsx b/web/components/layouts/Main.tsx index c8cae0b14..2cb8c1f71 100644 --- a/web/components/layouts/Main.tsx +++ b/web/components/layouts/Main.tsx @@ -1,6 +1,7 @@ import { Layout } from 'antd'; import { useRecoilValue } from 'recoil'; import Head from 'next/head'; +import { useEffect, useRef } from 'react'; import { ClientConfigStore, isChatAvailableSelector, @@ -11,6 +12,7 @@ import { Content, Header } from '../ui'; import { ClientConfig } from '../../interfaces/client-config.model'; import { DisplayableError } from '../../types/displayable-error'; import FatalErrorStateModal from '../modals/FatalErrorModal'; +import setupNoLinkReferrer from '../../utils/no-link-referrer'; function Main() { const clientConfig = useRecoilValue(clientConfigStateAtom); @@ -18,6 +20,12 @@ function Main() { const isChatAvailable = useRecoilValue(isChatAvailableSelector); const fatalError = useRecoilValue(fatalErrorStateAtom); + const layoutRef = useRef(null); + + useEffect(() => { + setupNoLinkReferrer(layoutRef.current); + }, []); + return ( <> @@ -80,7 +88,7 @@ function Main() { - +
{fatalError && ( diff --git a/web/utils/no-link-referrer.ts b/web/utils/no-link-referrer.ts new file mode 100644 index 000000000..9f3308fb1 --- /dev/null +++ b/web/utils/no-link-referrer.ts @@ -0,0 +1,44 @@ +/* +Due to Owncast's goal of being private by default, we don't want any external +links to leak the instance of Owncast as a referrer. +This observer attempts to catch any anchor tags and automatically add the +noopener and noreferrer attributes to them so the instance of Owncast isn't +passed along in the headers. + +This should should be fired somewhere relatively high level in the DOM and live +for the entirety of the page. +*/ + +/* eslint-disable no-restricted-syntax */ +export default function setupNoLinkReferrer(observationRoot: HTMLElement): void { + // Options for the observer (which mutations to observe) + const config = { attributes: false, childList: true, subtree: true }; + + const addNoReferrer = (node: Element): void => { + node.setAttribute('rel', 'noopener noreferrer '); + }; + + // Callback function to execute when mutations are observed + // eslint-disable-next-line func-names + const callback = function (mutationList) { + for (const mutation of mutationList) { + for (const node of mutation.addedNodes) { + // we track only elements, skip other nodes (e.g. text nodes) + // eslint-disable-next-line no-continue + if (!(node instanceof HTMLElement)) continue; + + if (node.tagName.toLowerCase() === 'a') { + addNoReferrer(node); + } + } + } + }; + + observationRoot.querySelectorAll('a').forEach(anchor => addNoReferrer(anchor)); + + // Create an observer instance linked to the callback function + const observer = new MutationObserver(callback); + + // Start observing the target node for configured mutations + observer.observe(observationRoot, config); +}