Merge pull request #2803 from mozilla/MNTOR-1115
MNTOR-1115: add sentry to v2
This commit is contained in:
Коммит
baf3657402
|
@ -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'
|
||||
|
|
|
@ -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 = { }
|
||||
|
|
38
src/app.js
38
src/app.js
|
@ -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 }
|
||||
|
|
Загрузка…
Ссылка в новой задаче