From ff51c14d840051b30deb1f08dd50a33c3ebe85fb Mon Sep 17 00:00:00 2001 From: Robert Helmer Date: Fri, 10 Feb 2023 16:52:05 -0800 Subject: [PATCH] * MNTOR-1042 (#2819) feat: set up GA4 with GTM --- .env-dist | 4 +++- src/app-constants.js | 3 ++- src/app.js | 26 ++++++++++++++++++++++---- src/controllers/breaches.js | 3 ++- src/controllers/dashboard.js | 3 ++- src/controllers/data-removal.js | 3 ++- src/controllers/landing.js | 3 ++- src/controllers/settings.js | 3 ++- src/views/main.js | 10 +++++++++- 9 files changed, 46 insertions(+), 12 deletions(-) diff --git a/.env-dist b/.env-dist index 13ef76fca..695864bf8 100755 --- a/.env-dist +++ b/.env-dist @@ -101,4 +101,6 @@ EDUCATION_VIDEO_URL_VPN=https://monitor.cdn.mozilla.net/videos/Mozilla_VPN.mp4 ADMINS= # Enable monthly cron-job, currently for sending unresolved breach reminder emails -MONTHLY_CRON_ENABLED= \ No newline at end of file +MONTHLY_CRON_ENABLED= + +GA4_MEASUREMENT_ID=test diff --git a/src/app-constants.js b/src/app-constants.js index b3f1046ef..4c006910c 100644 --- a/src/app-constants.js +++ b/src/app-constants.js @@ -46,7 +46,8 @@ const requiredEnvVars = [ 'DELETE_UNVERIFIED_SUBSCRIBERS_TIMER', 'EXPERIMENT_ACTIVE', 'MAX_NUM_ADDRESSES', - 'SUPPORTED_LOCALES' + 'SUPPORTED_LOCALES', + 'GA4_MEASUREMENT_ID' ] const optionalEnvVars = [ diff --git a/src/app.js b/src/app.js index d9b86f513..0ea70ac92 100644 --- a/src/app.js +++ b/src/app.js @@ -2,6 +2,8 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +import crypto from 'node:crypto' + import express from 'express' import session from 'express-session' import connectRedis from 'connect-redis' @@ -87,15 +89,31 @@ if (AppConstants.FXA_ENABLED) { imgSrc.push(fxaSrc) } +// Support GA4 per https://developers.google.com/tag-platform/tag-manager/web/csp +imgSrc.push('www.googletagmanager.com') + // disable forced https to allow localhost on Safari -app.use( +app.use((_req, res, _next) => { + res.locals.nonce = crypto.randomBytes(16).toString('hex') helmet.contentSecurityPolicy({ directives: { + upgradeInsecureRequests: isDev ? null : [], + scriptSrc: [ + "'self'", + // Support GA4 per https://developers.google.com/tag-platform/tag-manager/web/csp + `'nonce-${res.locals.nonce}'` + ], imgSrc, - upgradeInsecureRequests: isDev ? null : [] + connectSrc: [ + "'self'", + // Support GA4 per https://developers.google.com/tag-platform/tag-manager/web/csp + 'https://*.google-analytics.com', + 'https://*.analytics.google.com', + 'https://*.googletagmanager.com' + ] } - }) -) + })(_req, res, _next) +}) // fallback to default 'no-referrer' only when 'strict-origin-when-cross-origin' not available app.use( diff --git a/src/controllers/breaches.js b/src/controllers/breaches.js index f81901294..d5f8d58b6 100644 --- a/src/controllers/breaches.js +++ b/src/controllers/breaches.js @@ -20,7 +20,8 @@ async function breachesPage (req, res) { emailCount, partial: breaches, csrfToken: generateToken(res), - fxaProfile: req.user.fxa_profile_json + fxaProfile: req.user.fxa_profile_json, + nonce: res.locals.nonce } res.send(mainLayout(data)) diff --git a/src/controllers/dashboard.js b/src/controllers/dashboard.js index 2e6b26ae2..f135a9f4e 100644 --- a/src/controllers/dashboard.js +++ b/src/controllers/dashboard.js @@ -8,7 +8,8 @@ import { dashboard } from '../views/partials/dashboard.js' function dashboardPage (req, res) { const data = { fxaProfile: req.user.fxa_profile_json, - partial: dashboard + partial: dashboard, + nonce: res.locals.nonce } res.send(mainLayout(data)) diff --git a/src/controllers/data-removal.js b/src/controllers/data-removal.js index a4fed1a65..d425d14d3 100644 --- a/src/controllers/data-removal.js +++ b/src/controllers/data-removal.js @@ -8,7 +8,8 @@ import { dataRemoval } from '../views/partials/data-removal.js' function dataRemovalPage (req, res) { const data = { fxaProfile: req.user.fxa_profile_json, - partial: dataRemoval + partial: dataRemoval, + nonce: res.locals.nonce } res.send(mainLayout(data)) diff --git a/src/controllers/landing.js b/src/controllers/landing.js index 2c7c30d1d..5d21517c4 100644 --- a/src/controllers/landing.js +++ b/src/controllers/landing.js @@ -7,7 +7,8 @@ import { landing } from '../views/partials/landing.js' function landingPage (req, res) { const data = { - partial: landing + partial: landing, + nonce: res.locals.nonce } res.send(mainLayout(data)) diff --git a/src/controllers/settings.js b/src/controllers/settings.js index e4c254569..708dae8a3 100644 --- a/src/controllers/settings.js +++ b/src/controllers/settings.js @@ -62,7 +62,8 @@ async function settingsPage (req, res) { emails, breachCounts, limit: AppConstants.MAX_NUM_ADDRESSES, - csrfToken: generateToken(res) + csrfToken: generateToken(res), + nonce: res.locals.nonce } res.send(mainLayout(data)) diff --git a/src/views/main.js b/src/views/main.js index 8a6a4928d..df44f9e10 100644 --- a/src/views/main.js +++ b/src/views/main.js @@ -36,7 +36,15 @@ const mainLayout = data => ` - + + + + ${data.partial.name === 'landing' ? landingHeader(data) : mainHeader(data)}