Add noreferrer automatically to link tags. Closes #1941

This commit is contained in:
Gabe Kangas 2022-06-26 20:09:07 -07:00
parent b08393295f
commit 8fc922588b
No known key found for this signature in database
GPG key ID: 9A56337728BC81EA
2 changed files with 53 additions and 1 deletions

View file

@ -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<ClientConfig>(clientConfigStateAtom);
@ -18,6 +20,12 @@ function Main() {
const isChatAvailable = useRecoilValue<boolean>(isChatAvailableSelector);
const fatalError = useRecoilValue<DisplayableError>(fatalErrorStateAtom);
const layoutRef = useRef<HTMLDivElement>(null);
useEffect(() => {
setupNoLinkReferrer(layoutRef.current);
}, []);
return (
<>
<Head>
@ -80,7 +88,7 @@ function Main() {
</Head>
<ClientConfigStore />
<Layout>
<Layout ref={layoutRef}>
<Header name={title || name} chatAvailable={isChatAvailable} />
<Content />
{fatalError && (

View file

@ -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);
}