Merge pull request #2803 from mozilla/MNTOR-1115

MNTOR-1115: add sentry to v2
This commit is contained in:
mansaj 2023-02-10 08:43:47 -08:00 коммит произвёл GitHub
Родитель 6f0f6b0e47 5473540afd
Коммит baf3657402
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
12 изменённых файлов: 161 добавлений и 86 удалений

Просмотреть файл

@ -66,6 +66,7 @@ FX_REMOTE_SETTINGS_WRITER_PASS=
# DSN for Sentry error and event capturing
# e.g., SENTRY_DSN=https://{key}@sentry.prod.mozaws.net/408
SENTRY_DSN=
SENTRY_DSN_LEGACY=
BREACH_RESOLUTION_ENABLED=1
PRODUCT_PROMOS_ENABLED=1

Просмотреть файл

@ -40,7 +40,7 @@ const requiredEnvVars = [
'PAGE_TOKEN_TIMER',
'PRODUCT_PROMOS_ENABLED',
'REDIS_URL',
'SENTRY_DSN',
'SENTRY_DSN_LEGACY',
'DELETE_UNVERIFIED_SUBSCRIBERS_TIMER',
'EXPERIMENT_ACTIVE',
'MAX_NUM_ADDRESSES'

142
package-lock.json сгенерированный
Просмотреть файл

@ -15,7 +15,8 @@
"@fluent/bundle": "^0.17.1",
"@fluent/langneg": "^0.6.2",
"@maxmind/geoip2-node": "^3.1.0",
"@sentry/node": "7.12.0",
"@sentry/node": "7.36.0",
"@sentry/tracing": "^7.36.0",
"body-parser": "^1.20.1",
"client-oauth2": "4.3.3",
"connect-redis": "6.1.3",
@ -1267,24 +1268,12 @@
}
},
"node_modules/@sentry/core": {
"version": "7.12.0",
"license": "BSD-3-Clause",
"version": "7.36.0",
"resolved": "https://registry.npmjs.org/@sentry/core/-/core-7.36.0.tgz",
"integrity": "sha512-lq1MlcMhvm7QIwUOknFeufkg4M6QREY3s61y6pm1o+o3vSqB7Hz0D19xlyEpP62qMn8OyuttVKOVK1UfGc2EyQ==",
"dependencies": {
"@sentry/hub": "7.12.0",
"@sentry/types": "7.12.0",
"@sentry/utils": "7.12.0",
"tslib": "^1.9.3"
},
"engines": {
"node": ">=8"
}
},
"node_modules/@sentry/hub": {
"version": "7.12.0",
"license": "BSD-3-Clause",
"dependencies": {
"@sentry/types": "7.12.0",
"@sentry/utils": "7.12.0",
"@sentry/types": "7.36.0",
"@sentry/utils": "7.36.0",
"tslib": "^1.9.3"
},
"engines": {
@ -1292,13 +1281,13 @@
}
},
"node_modules/@sentry/node": {
"version": "7.12.0",
"license": "BSD-3-Clause",
"version": "7.36.0",
"resolved": "https://registry.npmjs.org/@sentry/node/-/node-7.36.0.tgz",
"integrity": "sha512-nAHAY+Rbn5OlTpNX/i6wYrmw3hT/BtwPZ/vNU52cKgw7CpeE1UrCeFjnPn18rQPB7lIh7x0vNvoaPrfemRzpSQ==",
"dependencies": {
"@sentry/core": "7.12.0",
"@sentry/hub": "7.12.0",
"@sentry/types": "7.12.0",
"@sentry/utils": "7.12.0",
"@sentry/core": "7.36.0",
"@sentry/types": "7.36.0",
"@sentry/utils": "7.36.0",
"cookie": "^0.4.1",
"https-proxy-agent": "^5.0.0",
"lru_map": "^0.3.3",
@ -1308,18 +1297,34 @@
"node": ">=8"
}
},
"node_modules/@sentry/tracing": {
"version": "7.36.0",
"resolved": "https://registry.npmjs.org/@sentry/tracing/-/tracing-7.36.0.tgz",
"integrity": "sha512-5R5mfWMDncOcTMmmyYMjgus1vZJzIFw4LHaSbrX7e1IRNT/6vFyNeVxATa2ePXb9mI3XHo5f2p7YrnreAtaSXw==",
"dependencies": {
"@sentry/core": "7.36.0",
"@sentry/types": "7.36.0",
"@sentry/utils": "7.36.0",
"tslib": "^1.9.3"
},
"engines": {
"node": ">=8"
}
},
"node_modules/@sentry/types": {
"version": "7.12.0",
"license": "BSD-3-Clause",
"version": "7.36.0",
"resolved": "https://registry.npmjs.org/@sentry/types/-/types-7.36.0.tgz",
"integrity": "sha512-uvfwUn3okAWSZ948D/xqBrkc3Sn6TeHUgi3+p/dTTNGAXXskzavgfgQ4rSW7f3YD4LL+boZojpoIARVLodMGuA==",
"engines": {
"node": ">=8"
}
},
"node_modules/@sentry/utils": {
"version": "7.12.0",
"license": "BSD-3-Clause",
"version": "7.36.0",
"resolved": "https://registry.npmjs.org/@sentry/utils/-/utils-7.36.0.tgz",
"integrity": "sha512-mgDi5X5Bm0sydCzXpnyKD/sD98yc2qnKXyRdNX4HRRwruhC/P53LT0hGhZXsyqsB/l8OAMl0zWXJLg0xONQsEw==",
"dependencies": {
"@sentry/types": "7.12.0",
"@sentry/types": "7.36.0",
"tslib": "^1.9.3"
},
"engines": {
@ -5804,8 +5809,9 @@
}
},
"node_modules/http-cache-semantics": {
"version": "4.1.0",
"license": "BSD-2-Clause"
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.1.tgz",
"integrity": "sha512-er295DKPVsV82j5kw1Gjt+ADA/XYHsajl82cGNQG2eyoPkvgUhX+nDIyelzhIWbbsXP39EHcI6l5tYs2FYqYXQ=="
},
"node_modules/http-errors": {
"version": "2.0.0",
@ -7335,9 +7341,9 @@
}
},
"node_modules/knex": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/knex/-/knex-2.3.0.tgz",
"integrity": "sha512-WMizPaq9wRMkfnwKXKXgBZeZFOSHGdtoSz5SaLAVNs3WRDfawt9O89T4XyH52PETxjV8/kRk0Yf+8WBEP/zbYw==",
"version": "2.4.2",
"resolved": "https://registry.npmjs.org/knex/-/knex-2.4.2.tgz",
"integrity": "sha512-tMI1M7a+xwHhPxjbl/H9K1kHX+VncEYcvCx5K00M16bWvpYPKAZd6QrCu68PtHAdIZNQPWZn0GVhqVBEthGWCg==",
"dependencies": {
"colorette": "2.0.19",
"commander": "^9.1.0",
@ -10564,7 +10570,8 @@
},
"node_modules/tslib": {
"version": "1.14.1",
"license": "0BSD"
"resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz",
"integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg=="
},
"node_modules/tsscmp": {
"version": "1.0.6",
@ -11957,42 +11964,51 @@
}
},
"@sentry/core": {
"version": "7.12.0",
"version": "7.36.0",
"resolved": "https://registry.npmjs.org/@sentry/core/-/core-7.36.0.tgz",
"integrity": "sha512-lq1MlcMhvm7QIwUOknFeufkg4M6QREY3s61y6pm1o+o3vSqB7Hz0D19xlyEpP62qMn8OyuttVKOVK1UfGc2EyQ==",
"requires": {
"@sentry/hub": "7.12.0",
"@sentry/types": "7.12.0",
"@sentry/utils": "7.12.0",
"tslib": "^1.9.3"
}
},
"@sentry/hub": {
"version": "7.12.0",
"requires": {
"@sentry/types": "7.12.0",
"@sentry/utils": "7.12.0",
"@sentry/types": "7.36.0",
"@sentry/utils": "7.36.0",
"tslib": "^1.9.3"
}
},
"@sentry/node": {
"version": "7.12.0",
"version": "7.36.0",
"resolved": "https://registry.npmjs.org/@sentry/node/-/node-7.36.0.tgz",
"integrity": "sha512-nAHAY+Rbn5OlTpNX/i6wYrmw3hT/BtwPZ/vNU52cKgw7CpeE1UrCeFjnPn18rQPB7lIh7x0vNvoaPrfemRzpSQ==",
"requires": {
"@sentry/core": "7.12.0",
"@sentry/hub": "7.12.0",
"@sentry/types": "7.12.0",
"@sentry/utils": "7.12.0",
"@sentry/core": "7.36.0",
"@sentry/types": "7.36.0",
"@sentry/utils": "7.36.0",
"cookie": "^0.4.1",
"https-proxy-agent": "^5.0.0",
"lru_map": "^0.3.3",
"tslib": "^1.9.3"
}
},
"@sentry/tracing": {
"version": "7.36.0",
"resolved": "https://registry.npmjs.org/@sentry/tracing/-/tracing-7.36.0.tgz",
"integrity": "sha512-5R5mfWMDncOcTMmmyYMjgus1vZJzIFw4LHaSbrX7e1IRNT/6vFyNeVxATa2ePXb9mI3XHo5f2p7YrnreAtaSXw==",
"requires": {
"@sentry/core": "7.36.0",
"@sentry/types": "7.36.0",
"@sentry/utils": "7.36.0",
"tslib": "^1.9.3"
}
},
"@sentry/types": {
"version": "7.12.0"
"version": "7.36.0",
"resolved": "https://registry.npmjs.org/@sentry/types/-/types-7.36.0.tgz",
"integrity": "sha512-uvfwUn3okAWSZ948D/xqBrkc3Sn6TeHUgi3+p/dTTNGAXXskzavgfgQ4rSW7f3YD4LL+boZojpoIARVLodMGuA=="
},
"@sentry/utils": {
"version": "7.12.0",
"version": "7.36.0",
"resolved": "https://registry.npmjs.org/@sentry/utils/-/utils-7.36.0.tgz",
"integrity": "sha512-mgDi5X5Bm0sydCzXpnyKD/sD98yc2qnKXyRdNX4HRRwruhC/P53LT0hGhZXsyqsB/l8OAMl0zWXJLg0xONQsEw==",
"requires": {
"@sentry/types": "7.12.0",
"@sentry/types": "7.36.0",
"tslib": "^1.9.3"
}
},
@ -14819,7 +14835,9 @@
"dev": true
},
"http-cache-semantics": {
"version": "4.1.0"
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.1.tgz",
"integrity": "sha512-er295DKPVsV82j5kw1Gjt+ADA/XYHsajl82cGNQG2eyoPkvgUhX+nDIyelzhIWbbsXP39EHcI6l5tYs2FYqYXQ=="
},
"http-errors": {
"version": "2.0.0",
@ -15832,9 +15850,9 @@
"dev": true
},
"knex": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/knex/-/knex-2.3.0.tgz",
"integrity": "sha512-WMizPaq9wRMkfnwKXKXgBZeZFOSHGdtoSz5SaLAVNs3WRDfawt9O89T4XyH52PETxjV8/kRk0Yf+8WBEP/zbYw==",
"version": "2.4.2",
"resolved": "https://registry.npmjs.org/knex/-/knex-2.4.2.tgz",
"integrity": "sha512-tMI1M7a+xwHhPxjbl/H9K1kHX+VncEYcvCx5K00M16bWvpYPKAZd6QrCu68PtHAdIZNQPWZn0GVhqVBEthGWCg==",
"requires": {
"colorette": "2.0.19",
"commander": "^9.1.0",
@ -16162,7 +16180,7 @@
"c8": "^7.12.0",
"csrf-csrf": "^2.2.2",
"esbuild": "^0.15.10",
"express-rate-limit": "*",
"express-rate-limit": "^6.7.0",
"helmet": "^6.0.0",
"redis-mock": "^0.56.3",
"svgo": "^2.8.0",
@ -17916,7 +17934,9 @@
}
},
"tslib": {
"version": "1.14.1"
"version": "1.14.1",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz",
"integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg=="
},
"tsscmp": {
"version": "1.0.6"

Просмотреть файл

@ -9,7 +9,8 @@
"@fluent/bundle": "^0.17.1",
"@fluent/langneg": "^0.6.2",
"@maxmind/geoip2-node": "^3.1.0",
"@sentry/node": "7.12.0",
"@sentry/node": "7.36.0",
"@sentry/tracing": "^7.36.0",
"body-parser": "^1.20.1",
"client-oauth2": "4.3.3",
"connect-redis": "6.1.3",

Просмотреть файл

@ -4,7 +4,7 @@
const Sentry = require('@sentry/node')
const AppConstants = require('./app-constants')
Sentry.init({
dsn: AppConstants.SENTRY_DSN,
dsn: AppConstants.SENTRY_DSN_LEGACY,
environment: AppConstants.NODE_ENV,
beforeSend (event, hint) {
if (!hint.originalException.locales || hint.originalException.locales[0] === 'en') return event // return if no localization or localization is in english

Просмотреть файл

@ -58,7 +58,8 @@ const optionalEnvVars = [
'GEOIP_GEOLITE2_COUNTRY_FILENAME',
'VPN_PROMO_BLOCKED_LOCALES',
'EDUCATION_VIDEO_URL_RELAY',
'MONITOR_V2'
'MONITOR_V2',
'SENTRY_DSN_LEGACY'
]
const AppConstants = { }

Просмотреть файл

@ -10,19 +10,39 @@ import accepts from 'accepts'
import redis from 'redis'
import cookieParser from 'cookie-parser'
import rateLimit from 'express-rate-limit'
import Sentry from '@sentry/node'
import '@sentry/tracing'
import AppConstants from './app-constants.js'
import { localStorage } from './utils/local-storage.js'
import { errorHandler } from './middleware/error.js'
import { doubleCsrfProtection } from './utils/csrf.js'
import { initFluentBundles, updateLocale } from './utils/fluent.js'
import { initFluentBundles, updateLocale, getMessageWithLocale, getMessage } from './utils/fluent.js'
import { loadBreachesIntoApp } from './utils/hibp.js'
import { RateLimitError } from './utils/error.js'
import { initEmail } from './utils/email.js'
import indexRouter from './routes/index.js'
const app = express()
const isDev = AppConstants.NODE_ENV === 'dev'
// init sentry
Sentry.init({
dsn: AppConstants.SENTRY_DSN,
environment: AppConstants.NODE_ENV,
debug: isDev,
beforeSend (event, hint) {
if (!hint.originalException.locales || hint.originalException.locales[0] === 'en') return event // return if no localization or localization is in english
// try to force an english translation for the error message if localized
if (hint.originalException.fluentID) {
event.exception.values[0].value = getMessageWithLocale(hint.originalException.fluentID, 'en') || getMessage(hint.originalException.fluentID)
}
return event
}
})
// Determine from where to serve client code/assets:
// Build script is triggered for `npm start` and assets are served from /dist.
// Build script is NOT run for `npm run dev`, assets are served from /src, and nodemon restarts server without build (faster dev).
@ -51,6 +71,13 @@ app.use(
})
)
app.use(
Sentry.Handlers.requestHandler({
request: ['headers', 'method', 'url'], // omit cookies, data, query_string
user: ['id'] // omit username, email
})
)
const imgSrc = [
"'self'"
]
@ -135,6 +162,15 @@ app.use('/api', apiLimiter)
// routing
app.use('/', indexRouter)
// sentry error handler
app.use(Sentry.Handlers.errorHandler({
shouldHandleError (error) {
if (error instanceof RateLimitError) return true
}
}))
// app error handler
app.use(errorHandler)
app.listen(AppConstants.PORT, async function () {

Просмотреть файл

@ -11,7 +11,8 @@ import { addSubscriber } from '../db/tables/email_addresses.js'
// import { sendEmail, getEmailCtaHref, getUnsubscribeUrl } from '../email-utils'
import { getProfileData, FxAOAuthClient } from '../utils/fxa.js'
// import { getBreachesForEmail } from '../utils/hibp.js'
import { fluentError } from '../utils/fluent.js'
import { getMessage } from '../utils/fluent.js'
import { UnauthorizedError } from '../utils/error.js'
import mozlog from '../utils/log.js'
const { SERVER_URL } = AppConstants
@ -41,12 +42,12 @@ function init (req, res, next, client = FxAOAuthClient) {
async function confirmed (req, res, next, client = FxAOAuthClient) {
if (!req.session.state) {
log.error('oauth-invalid-session', 'req.session.state missing')
throw fluentError('oauth-invalid-session')
throw new UnauthorizedError(getMessage('oauth-invalid-session'))
}
if (req.session.state !== req.query.state) {
log.error('oauth-invalid-session', 'req.session does not match req.query')
throw fluentError('oauth-invalid-session')
throw new UnauthorizedError(getMessage('oauth-invalid-session'))
}
const fxaUser = await client.code.getToken(req.originalUrl, { state: req.session.state })

Просмотреть файл

@ -4,6 +4,7 @@
import AppConstants from '../app-constants.js'
import { getBreachByName, loadBreachesIntoApp } from '../utils/hibp.js'
import { UnauthorizedError, UserInputError } from '../utils/error.js'
import mozlog from '../utils/log.js'
const log = mozlog('controllers.hibp')
@ -20,10 +21,10 @@ const log = mozlog('controllers.hibp')
async function notify (req, res) {
if (!req.token || req.token !== AppConstants.HIBP_NOTIFY_TOKEN) {
const errorMessage = 'HIBP notify endpoint requires valid authorization token.'
throw new Error(errorMessage)
throw new UnauthorizedError(errorMessage)
}
if (!['breachName', 'hashPrefix', 'hashSuffixes'].every(req.body?.hasOwnProperty, req.body)) {
throw new Error('HIBP breach notification: requires breachName, hashPrefix, and hashSuffixes.')
throw new UserInputError('HIBP breach notification: requires breachName, hashPrefix, and hashSuffixes.')
}
const { breachName } = req.body

Просмотреть файл

@ -15,12 +15,12 @@ import {
import { setAllEmailsToPrimary } from '../db/tables/subscribers.js'
import { fluentError, getMessage } from '../utils/fluent.js'
import { getMessage } from '../utils/fluent.js'
import { sendEmail, getVerificationUrl, getUnsubscribeUrl } from '../utils/email.js'
import { getBreachesForEmail } from '../utils/hibp.js'
import { generateToken } from '../utils/csrf.js'
import { RateLimitError } from '../utils/error.js'
import { RateLimitError, UnauthorizedError, UserInputError } from '../utils/error.js'
import { mainLayout } from '../views/main.js'
import { settings } from '../views/partials/settings.js'
@ -76,12 +76,11 @@ async function addEmail (req, res) {
const emailRegex = /^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$/
if (!email || !emailRegex.test(email)) {
throw fluentError('user-add-invalid-email')
throw new UserInputError(getMessage('user-add-invalid-email'))
}
// Total max number of email addresses minus one to account for the primary email
if (sessionUser.email_addresses.length >= AppConstants.MAX_NUM_ADDRESSES - 1) {
throw fluentError('user-add-too-many-emails')
throw new UserInputError(getMessage('user-add-too-many-emails'))
}
checkForDuplicateEmail(sessionUser, email)
@ -103,12 +102,12 @@ async function addEmail (req, res) {
function checkForDuplicateEmail (sessionUser, email) {
const emailLowerCase = email.toLowerCase()
if (emailLowerCase === sessionUser.primary_email.toLowerCase()) {
throw fluentError('user-add-duplicate-email')
throw new UserInputError(getMessage('user-add-duplicate-email'))
}
for (const secondaryEmail of sessionUser.email_addresses) {
if (emailLowerCase === secondaryEmail.email.toLowerCase()) {
throw fluentError('user-add-duplicate-email')
throw new UserInputError(getMessage('user-add-duplicate-email'))
}
}
}
@ -119,7 +118,7 @@ async function removeEmail (req, res) {
const existingEmail = await getEmailById(emailId)
if (existingEmail.subscriber_id !== sessionUser.id) {
throw fluentError('error-not-subscribed')
throw new UserInputError(getMessage('error-not-subscribed'))
}
removeOneSecondaryEmail(emailId)
@ -136,7 +135,7 @@ async function resendEmail (req, res) {
)
if (!filteredEmail) {
throw fluentError('user-verify-token-error')
throw new UnauthorizedError(getMessage('user-verify-token-error'))
}
await sendVerificationEmail(emailId)

Просмотреть файл

@ -226,15 +226,15 @@ test.serial('user add request with invalid email throws error', async t => {
})
const resp = createResponse()
const { fluentError } = await import('../utils/fluent.js')
td.when(fluentError('user-add-invalid-email')).thenThrow(new Error('user-add-invalid-email'))
const { getMessage } = await import('../utils/fluent.js')
td.when(getMessage('user-add-invalid-email')).thenReturn('invalid email')
// Call code-under-test
const { addEmail } = await import('./settings.js')
await t.throwsAsync(
addEmail(req, resp),
{ instanceOf: Error, message: 'user-add-invalid-email' }
{ instanceOf: Error, message: 'invalid email' }
)
})

Просмотреть файл

@ -79,7 +79,7 @@ function getRawMessage (id) {
}
/**
* Translate and transform a message pattern
* Translate and transform a message pattern with current locale
* Defaults to en if message id not found in requested locale
* @param {string} id - The Fluent message id.
* @param {object} args - key/value pairs corresponding to pattern in Fluent resource.
@ -89,7 +89,22 @@ function getRawMessage (id) {
* // Returns "Hello, Jane!"
*/
function getMessage (id, args) {
let bundle = fluentBundles[getLocale()]
return getMessageWithLocale(id, getLocale(), args)
}
/**
* Translate and transform a message pattern
* Can pass in any locale
* Defaults to en if message id not found in requested locale
* @param {string} id - The Fluent message id.
* @param {object} args - key/value pairs corresponding to pattern in Fluent resource.
* @example
* // Given FluentResource("hello = Hello, {$name}!")
* getMessage (hello, {name: "Jane"})
* // Returns "Hello, Jane!"
*/
function getMessageWithLocale (id, locale, args) {
let bundle = fluentBundles[locale]
if (!bundle.hasMessage(id)) bundle = fluentBundles.en
@ -102,4 +117,4 @@ function fluentError (id) {
return new Error(getMessage(id))
}
export { initFluentBundles, updateLocale, getLocale, getMessage, getRawMessage, fluentError }
export { initFluentBundles, updateLocale, getLocale, getMessage, getMessageWithLocale, getRawMessage, fluentError }