Родитель
0c1f2ef77e
Коммит
ff51c14d84
|
@ -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=
|
||||
MONTHLY_CRON_ENABLED=
|
||||
|
||||
GA4_MEASUREMENT_ID=test
|
||||
|
|
|
@ -46,7 +46,8 @@ const requiredEnvVars = [
|
|||
'DELETE_UNVERIFIED_SUBSCRIBERS_TIMER',
|
||||
'EXPERIMENT_ACTIVE',
|
||||
'MAX_NUM_ADDRESSES',
|
||||
'SUPPORTED_LOCALES'
|
||||
'SUPPORTED_LOCALES',
|
||||
'GA4_MEASUREMENT_ID'
|
||||
]
|
||||
|
||||
const optionalEnvVars = [
|
||||
|
|
26
src/app.js
26
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(
|
||||
|
|
|
@ -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))
|
||||
|
|
|
@ -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))
|
||||
|
|
|
@ -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))
|
||||
|
|
|
@ -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))
|
||||
|
|
|
@ -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))
|
||||
|
|
|
@ -36,7 +36,15 @@ const mainLayout = data => `
|
|||
<link rel='apple-touch-icon' href='/images/apple-touch-icon.webp' sizes='180x180'>
|
||||
|
||||
<script src='/js/index.js' type='module'></script>
|
||||
</head>
|
||||
<!-- Google Tag Manager -->
|
||||
<script nonce='${data.nonce}'>(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;var n=d.querySelector('[nonce]');
|
||||
n&&j.setAttribute('nonce',n.nonce||n.getAttribute('nonce'));f.parentNode.insertBefore(j,f);
|
||||
})(window,document,'script','dataLayer','${AppConstants.GA4_MEASUREMENT_ID}');</script>
|
||||
<!-- End Google Tag Manager -->
|
||||
</head>
|
||||
<body>
|
||||
${data.partial.name === 'landing' ? landingHeader(data) : mainHeader(data)}
|
||||
<main data-partial='${data.partial.name}'>
|
||||
|
|
Загрузка…
Ссылка в новой задаче