diff --git a/functions/_middleware.ts b/functions/_middleware.ts index 7672f8665..5bc0fc30d 100644 --- a/functions/_middleware.ts +++ b/functions/_middleware.ts @@ -1,4 +1,5 @@ /* eslint max-classes-per-file: "off" */ +import { cspOnlyFrameAncestors, cspWithFrameAncestors } from '@app/utils/createCsp' class ContentModifier { private newContent: string @@ -57,20 +58,14 @@ const firefoxRewrite: PagesFunction = async ({ request, next }) => { // firefox CSP exception + metamask script if (userAgent?.includes('gecko/20100101') && userAgent.includes('firefox/')) { - response.headers.set( - 'Content-Security-Policy', - "frame-ancestors 'self' https://app.safe.global;", - ) + response.headers.set('Content-Security-Policy', cspOnlyFrameAncestors) return new HTMLRewriter() .on('head', new ScriptWriter('/_next/static/chunks/initialise-metamask.js')) .transform(response) } // default headers - response.headers.set( - 'Content-Security-Policy', - "worker-src 'self'; script-src 'self' 'sha256-UyYcl+sKCF/ROFZPHBlozJrndwfNiC5KT5ZZfup/pPc=' plausible.io static.cloudflareinsights.com *.ens-app-v3.pages.dev https://app.intercom.io https://widget.intercom.io https://js.intercomcdn.com 'wasm-unsafe-eval'; frame-ancestors 'self' https://app.safe.global;", - ) + response.headers.set('Content-Security-Policy', cspWithFrameAncestors) return response } diff --git a/functions/tsconfig.json b/functions/tsconfig.json index 1680095d9..17ac1f51a 100644 --- a/functions/tsconfig.json +++ b/functions/tsconfig.json @@ -1,5 +1,5 @@ { - "include": ["../src/utils/gradient.ts", "./**/*"], + "include": ["../src/utils/createCsp.ts", "./**/*"], "compilerOptions": { "noEmit": true, "target": "ESNext", diff --git a/src/pages/_document.tsx b/src/pages/_document.tsx index ddad79b2d..3c04a39ed 100644 --- a/src/pages/_document.tsx +++ b/src/pages/_document.tsx @@ -3,6 +3,8 @@ import { AppPropsType, AppType } from 'next/dist/shared/lib/utils' import Document, { DocumentContext, Head, Html, Main, NextScript } from 'next/document' import { ServerStyleSheet, StyleSheetManager } from 'styled-components' +import { cspWithoutFrameAncestors } from '@app/utils/createCsp' + const ipfsPathScript = ` (function () { const { pathname } = window.location @@ -14,6 +16,7 @@ const ipfsPathScript = ` })(); ` +// sha256-UyYcl+sKCF/ROFZPHBlozJrndwfNiC5KT5ZZfup/pPc= const hiddenCheckScript = ` if (document.prerendering) { document.addEventListener('prerenderingchange', () => { @@ -34,6 +37,36 @@ const hiddenCheckScript = ` } ` +// sha256-84jekTLuMPFFzbBxEFpoUhJbu81z5uBinvhIKKkAPxg= +const themeSwitcherScript = ` + (function () { + function setTheme(newTheme) { + document.documentElement.setAttribute('data-theme', newTheme); + window.__theme = newTheme; + window.__onThemeChange(newTheme); + } + window.__onThemeChange = function () {}; + window.__setPreferredTheme = function (newTheme) { + setTheme(newTheme); + try { + localStorage.setItem("theme", JSON.stringify(window.__theme)); + } catch (err) {} + }; + + const darkQuery = window.matchMedia("(prefers-color-scheme: dark)"); + darkQuery.addListener(function (event) { + window.__setPreferredTheme(event.matches ? "dark" : "light"); + }); + + let preferredTheme; + try { + preferredTheme = JSON.parse(localStorage.getItem("theme")); + } catch (err) {} + + setTheme(preferredTheme || (darkQuery.matches ? "dark" : "light")); + })(); +` + const makeIPFSURL = (url: string) => { if (process.env.NEXT_PUBLIC_IPFS) { return `.${url}` @@ -76,41 +109,12 @@ export default class MyDocument extends Document {
{process.env.NODE_ENV === 'production' && ( - + )} {process.env.NEXT_PUBLIC_IPFS && ( diff --git a/src/utils/createCsp.ts b/src/utils/createCsp.ts new file mode 100644 index 000000000..8844e465b --- /dev/null +++ b/src/utils/createCsp.ts @@ -0,0 +1,49 @@ +let csp = '' + +// worker-src +csp += 'worker-src' +// only self worker/service worker +csp += " 'self'" +// end worker-src +csp += ';' + +// script-src +csp += ' script-src' +// allow self +csp += " 'self'" +// allow plausible script +csp += ' plausible.io' +// allow cloudflare analytics script +csp += ' static.cloudflareinsights.com' +// allow loading from the pages domain for this app +csp += ' *.ens-app-v3.pages.dev' +// allow intercom scripts +csp += ' https://app.intercom.io' +csp += ' https://widget.intercom.io' +csp += ' https://js.intercomcdn.com' +// allow inline wasm evaluation +csp += ' wasm-unsafe-eval' +// INLINE SCRIPT HASHES +// hiddenCheckScript +csp += " 'sha256-UyYcl+sKCF/ROFZPHBlozJrndwfNiC5KT5ZZfup/pPc='" +// themeSwitcherScript +csp += " 'sha256-84jekTLuMPFFzbBxEFpoUhJbu81z5uBinvhIKKkAPxg='" +// end script-src +csp += ';' + +// for use with csp meta tag +export const cspWithoutFrameAncestors = csp + +let frameAncestors = '' + +// frame-ancestors +frameAncestors += 'frame-ancestors' +// allow self +frameAncestors += " 'self'" +// allow safe wallet +frameAncestors += ' https://app.safe.global' +// end frame-ancestors +frameAncestors += ';' + +export const cspOnlyFrameAncestors = frameAncestors +export const cspWithFrameAncestors = `${csp} ${frameAncestors}`