From 81b7d8c3aa2a77728c0aa7b59c052ac6cad51146 Mon Sep 17 00:00:00 2001 From: Mavis Ou Date: Thu, 1 Dec 2022 10:23:17 -0800 Subject: [PATCH] Added nonce & implemented GTM and CSP changes (#1661) * Added nonce & implemented GTM and CSP changes --- js/analytics.js | 20 +-------- js/security-headers.js | 95 +++++++++++++++++++++++------------------- server.js | 43 ++++++++++++++++--- 3 files changed, 89 insertions(+), 69 deletions(-) diff --git a/js/analytics.js b/js/analytics.js index b4e5bc3..7f1a0a8 100644 --- a/js/analytics.js +++ b/js/analytics.js @@ -50,25 +50,7 @@ if (checkDoNotTrack() === false) { return identifier; }; - const GA_ID = getIdentifier(`ga`); - const GTM_ID = getIdentifier(`gtm`); - - if (GA_ID) { - ReactGA.initialize(GA_ID); - } - - if (GTM_ID) { - (function (w, d, s, l, i) { - w[l] = w[l] || []; - w[l].push({ "gtm.start": new Date().getTime(), event: "gtm.js" }); - var f = d.getElementsByTagName(s)[0], - j = d.createElement(s), - dl = l != "dataLayer" ? "&l=" + l : ""; - j.async = true; - j.src = "https://www.googletagmanager.com/gtm.js?id=" + i + dl; - f.parentNode.insertBefore(j, f); - })(window, document, "script", "dataLayer", GTM_ID); - } + ReactGA.initialize(getIdentifier(`ga`)); }, }; } diff --git a/js/security-headers.js b/js/security-headers.js index 0e8d3d9..a5037d5 100644 --- a/js/security-headers.js +++ b/js/security-headers.js @@ -1,47 +1,54 @@ import url from "url"; import { env } from "./env-server"; -export default { - directives: { - defaultSrc: [`'none'`], - scriptSrc: [ - `'self'`, - `https://*.google-analytics.com`, - `https://platform.twitter.com/widgets.js`, - `https://www.googletagmanager.com/gtm.js`, - `https://www.googletagmanager.com/debug/bootstrap`, - `https://www.google.com/recaptcha/api.js`, - `https://www.gstatic.com/recaptcha/releases/`, - ], - fontSrc: [ - `'self'`, - `https://code.cdn.mozilla.net`, - `https://fonts.gstatic.com`, - ], - frameSrc: [`https://www.google.com/`, `https://platform.twitter.com/`], - styleSrc: [ - `'self'`, - `'unsafe-inline'`, - `https://code.cdn.mozilla.net`, - `https://fonts.googleapis.com`, - ], - imgSrc: [`'self'`, `data:`, `https:`, `http:`], - connectSrc: [ - `'self'`, - url.parse(env.PULSE_API).host || - `https://network-pulse-api-staging.herokuapp.com/`, - `https://www.mozilla.org/en-US/newsletter/`, - `https://www.google.com/recaptcha/api.js`, - `https://www.gstatic.com/recaptcha/releases/`, - `https://www.google-analytics.com/j/collect`, - ], - childSrc: [ - `https://syndication.twitter.com`, - `https://platform.twitter.com`, - ], - frameAncestors: [`'none'`], - manifestSrc: [`'self'`], - }, - reportOnly: false, - browserSniff: false, -}; +export default function (cspNonce) { + return { + directives: { + defaultSrc: [`'none'`], + scriptSrc: [ + `'self'`, + `'nonce-${cspNonce}'`, + `https://www.google-analytics.com`, + `https://platform.twitter.com/widgets.js`, + `https://*.googletagmanager.com`, + `https://www.google.com/recaptcha/api.js`, + `https://www.gstatic.com/recaptcha/releases/`, + `https://tagmanager.google.com`, + ], + fontSrc: [ + `'self'`, + `https://code.cdn.mozilla.net`, + `https://fonts.gstatic.com`, + `data:`, + ], + frameSrc: [`https://www.google.com/`, `https://platform.twitter.com/`], + styleSrc: [ + `'self'`, + `'unsafe-inline'`, + `https://code.cdn.mozilla.net`, + `https://fonts.googleapis.com`, + `https://tagmanager.google.com`, + ], + imgSrc: [`'self'`, `data:`, `https:`, `http:`], + connectSrc: [ + `'self'`, + url.parse(env.PULSE_API).host || + `https://network-pulse-api-staging.herokuapp.com/`, + `https://www.mozilla.org/en-US/newsletter/`, + `https://www.google.com/recaptcha/api.js`, + `https://www.gstatic.com/recaptcha/releases/`, + `https://*.google-analytics.com`, + `https://*.analytics.google.com`, + `https://*.googletagmanager.com`, + ], + childSrc: [ + `https://syndication.twitter.com`, + `https://platform.twitter.com`, + ], + frameAncestors: [`'none'`], + manifestSrc: [`'self'`], + }, + reportOnly: false, + browserSniff: false, + }; +} diff --git a/server.js b/server.js index 939cfa8..f3ad005 100644 --- a/server.js +++ b/server.js @@ -9,6 +9,10 @@ import { StaticRouter } from "react-router"; import Main from "./main.jsx"; import securityHeaders from "./js/security-headers"; import { envUtilities, env } from "./js/env-server"; +import crypto from "crypto"; + +const GA_ID = `UA-87658599-4`; +const GTM_ID = `GTM-KHLX47C`; const app = express(); @@ -39,8 +43,19 @@ const hstsMiddleware = helmet.hsts({ // disable x-powered-by app.disable(`x-powered-by`); -// Some app security settings -app.use(helmet.contentSecurityPolicy(securityHeaders)); +// app security settings + +app.use((req, res, next) => { + // generate unique nonce for every response + res.locals.cspNonce = crypto.randomBytes(16).toString("hex"); + + // pass the same nonce to CSP + const cspMiddleware = helmet.contentSecurityPolicy( + securityHeaders(res.locals.cspNonce) + ); + + cspMiddleware(res, res, next); +}); app.use( helmet.xssFilter({ @@ -98,7 +113,7 @@ if (NODE_ENV !== `development`) { }); } -function renderPage(appHtml, reactHelmet, canonicalUrl) { +function renderPage(appHtml, reactHelmet, canonicalUrl, cspNonce) { const meta = { url: canonicalUrl, title: `Mozilla Pulse`, @@ -167,14 +182,28 @@ function renderPage(appHtml, reactHelmet, canonicalUrl) { `` )}rss/latest"> ${reactHelmet.title.toString()} - - + + ${recaptcha}
${appHtml}
+ `; } @@ -197,7 +226,9 @@ app.get(`*`, (req, res) => { res .status(context.pageNotFound ? 404 : 200) - .send(renderPage(appHtml, reactHelmet, canonicalUrl)); + .send( + renderPage(appHtml, reactHelmet, canonicalUrl, res.locals.cspNonce) + ); } });