• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

mozilla / blurts-server / e12065f2-5c0b-4697-b6a1-e1341a5d3c0b

pending completion
e12065f2-5c0b-4697-b6a1-e1341a5d3c0b

push

circleci

Vincent
Remove unused import

282 of 1373 branches covered (20.54%)

Branch coverage included in aggregate %.

959 of 3720 relevant lines covered (25.78%)

4.19 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

0.0
/server.js
1
'use strict'
2

3
// initialize Sentry ASAP to capture fatal startup errors
4
const Sentry = require('@sentry/node')
×
5
const AppConstants = require('./app-constants')
×
6
Sentry.init({
×
7
  dsn: AppConstants.SENTRY_DSN_LEGACY,
8
  environment: AppConstants.NODE_ENV,
9
  beforeSend (event, hint) {
10
    if (!hint.originalException.locales || hint.originalException.locales[0] === 'en') return event // return if no localization or localization is in english
×
11

12
    try {
×
13
      if (hint.originalException.fluentID) {
×
14
        event.exception.values[0].value = LocaleUtils.fluentFormat(['en'], hint.originalException.fluentID)
×
15
      }
16
    } catch (e) {
17
      return event
×
18
    }
19

20
    return event
×
21
  }
22
})
23

24
const connectRedis = require('connect-redis')
×
25
const express = require('express')
×
26
const exphbs = require('express-handlebars')
×
27
const helmet = require('helmet')
×
28
const session = require('express-session')
×
29
const cookieParser = require('cookie-parser')
×
30
const { URL } = require('url')
×
31
const path = require('path')
×
32

33
const EmailUtils = require('./email-utils')
×
34
const HBSHelpers = require('./template-helpers/')
×
35
const HIBP = require('./hibp')
×
36
const IpLocationService = require('./ip-location-service')
×
37

38
const {
39
  addRequestToResponse, pickLanguage, logErrors, localizeErrorMessages,
40
  clientErrorHandler, errorHandler, recordVisitFromEmail
41
} = require('./middleware')
×
42
const { LocaleUtils } = require('./locale-utils')
×
43
const mozlog = require('./log')
×
44

45
const DockerflowRoutes = require('./routes/dockerflow')
×
46
const HibpRoutes = require('./routes/hibp')
×
47
const HomeRoutes = require('./routes/home')
×
48
const ScanRoutes = require('./routes/scan')
×
49
const SesRoutes = require('./routes/ses')
×
50
const OAuthRoutes = require('./routes/oauth')
×
51
const UserRoutes = require('./routes/user')
×
52
const EmailL10nRoutes = require('./routes/email-l10n')
×
53
const BreachRoutes = require('./routes/breach-details')
×
54

55
const log = mozlog('server')
×
56

57
function getRedisStore () {
58
  const RedisStoreConstructor = connectRedis(session)
×
59
  if (['', 'redis-mock'].includes(AppConstants.REDIS_URL)) {
×
60
    const redis = require('redis-mock')
×
61
    return new RedisStoreConstructor({ client: redis.createClient() })
×
62
  }
63
  const redis = require('redis')
×
64
  return new RedisStoreConstructor({ client: redis.createClient({ url: AppConstants.REDIS_URL }) })
×
65
}
66

67
const app = express()
×
68
app.use(
×
69
  Sentry.Handlers.requestHandler({
70
    request: ['headers', 'method', 'url'], // omit cookies, data, query_string
71
    user: ['id'] // omit username, email
72
  })
73
)
74

75
function devOrHeroku () {
76
  return ['dev', 'heroku'].includes(AppConstants.NODE_ENV)
×
77
}
78

79
if (app.get('env') !== 'dev') {
×
80
  app.enable('trust proxy')
×
81
  app.use((req, res, next) => {
×
82
    if (req.secure) {
×
83
      next()
×
84
    } else {
85
      res.redirect('https://' + req.headers.host + req.url)
×
86
    }
87
  })
88
}
89

90
try {
×
91
  LocaleUtils.init()
×
92
  LocaleUtils.loadLanguagesIntoApp(app)
×
93
} catch (error) {
94
  log.error('try-load-languages-error', { error })
×
95
}
96

97
(async () => {
×
98
  try {
×
99
    await HIBP.loadBreachesIntoApp(app)
×
100
  } catch (error) {
101
    log.error('try-load-breaches-error', { error })
×
102
  }
103
})();
104

105
(async () => {
×
106
  // open location database once at server start. Service includes 24hr check to reload fresh database.
107
  await IpLocationService.openLocationDb().catch(e => console.warn(e))
×
108
})()
109

110
// Use helmet to set security headers
111
// only enable HSTS on heroku; Ops handles it in stage & prod configs
112
if (AppConstants.NODE_ENV === 'heroku') {
×
113
  app.use(helmet.hsts({ maxAge: 60 * 60 * 24 * 365 * 2 })) // 2 years
×
114
}
115

116
const SCRIPT_SOURCES = ["'self'", 'https://www.google-analytics.com/analytics.js']
×
117
const STYLE_SOURCES = ["'self'", 'https://code.cdn.mozilla.net/fonts/']
×
118
const FRAME_ANCESTORS = ["'none'"]
×
119

120
app.locals.ENABLE_PONTOON_JS = false
×
121
// Allow pontoon.mozilla.org on heroku for in-page localization
122
const PONTOON_DOMAIN = 'https://pontoon.mozilla.org'
×
123
if (AppConstants.NODE_ENV === 'heroku') {
×
124
  app.locals.ENABLE_PONTOON_JS = true
×
125
  SCRIPT_SOURCES.push(PONTOON_DOMAIN)
×
126
  STYLE_SOURCES.push(PONTOON_DOMAIN)
×
127
  FRAME_ANCESTORS[0] = PONTOON_DOMAIN // other sources cannot be declared alongside 'none'
×
128
}
129

130
const imgSrc = [
×
131
  "'self'",
132
  'https://www.google-analytics.com',
133
  'https://firefoxusercontent.com',
134
  'https://mozillausercontent.com/',
135
  'https://monitor.cdn.mozilla.net/'
136
]
137

138
const connectSrc = [
×
139
  "'self'",
140
  'https://code.cdn.mozilla.net/fonts/',
141
  'https://www.google-analytics.com',
142
  'https://accounts.firefox.com',
143
  'https://accounts.stage.mozaws.net/metrics-flow',
144
  'https://am.i.mullvad.net/json'
145
]
146

147
if (AppConstants.FXA_ENABLED) {
×
148
  const fxaSrc = new URL(AppConstants.OAUTH_PROFILE_URI).origin;
×
149
  [imgSrc, connectSrc].forEach(arr => {
×
150
    arr.push(fxaSrc)
×
151
  })
152
}
153

154
app.use(
×
155
  helmet.contentSecurityPolicy({
156
    directives: {
157
      baseUri: ["'none'"],
158
      defaultSrc: ["'self'"],
159
      connectSrc,
160
      fontSrc: [
161
        "'self'",
162
        'https://fonts.gstatic.com/',
163
        'https://code.cdn.mozilla.net/fonts/'
164
      ],
165
      frameAncestors: FRAME_ANCESTORS,
166
      mediaSrc: [
167
        "'self'",
168
        'https://monitor.cdn.mozilla.net/'
169
      ],
170
      formAction: ["'self'"],
171
      imgSrc,
172
      objectSrc: ["'none'"],
173
      scriptSrc: SCRIPT_SOURCES,
174
      styleSrc: STYLE_SOURCES,
175
      reportUri: '/__cspreport__'
176
    }
177
  })
178
)
179
app.use(helmet.referrerPolicy({ policy: 'strict-origin-when-cross-origin' }))
×
180

181
// helmet no longer sets X-Content-Type-Options, so set it manually
182
app.use((req, res, next) => {
×
183
  res.setHeader('X-Content-Type-Options', 'nosniff')
×
184
  next()
×
185
})
186

187
app.use(express.static('public', {
×
188
  setHeaders: res => res.set('Cache-Control',
×
189
    'public, maxage=' + 10 * 60 * 1000 + ', s-maxage=' + 24 * 60 * 60 * 1000)
190
})) // 10-minute client-side caching; 24-hour server-side caching
191

192
app.use(cookieParser())
×
193

194
const hbs = exphbs.create({
×
195
  extname: '.hbs',
196
  layoutsDir: path.join(__dirname, '/views/layouts'),
197
  defaultLayout: 'default',
198
  partialsDir: [path.join(__dirname, '/views/layouts'), path.join(__dirname, '/views/partials')],
199
  helpers: HBSHelpers.helpers
200
})
201
app.engine('hbs', hbs.engine)
×
202
app.set('view engine', 'hbs')
×
203

204
// TODO: refactor all templates to use constants.VAR
205
// instead of assigning these 1-by-1 to app.locales
206
app.locals.constants = AppConstants
×
207
app.locals.FXA_ENABLED = AppConstants.FXA_ENABLED
×
208
app.locals.SERVER_URL = AppConstants.SERVER_URL
×
209
app.locals.MAX_NUM_ADDRESSES = AppConstants.MAX_NUM_ADDRESSES
×
210
app.locals.EXPERIMENT_ACTIVE = AppConstants.EXPERIMENT_ACTIVE
×
211
app.locals.RECRUITMENT_BANNER_LINK = AppConstants.RECRUITMENT_BANNER_LINK
×
212
app.locals.RECRUITMENT_BANNER_TEXT = AppConstants.RECRUITMENT_BANNER_TEXT
×
213
app.locals.LOGOS_ORIGIN = AppConstants.LOGOS_ORIGIN
×
214
app.locals.UTM_SOURCE = new URL(AppConstants.SERVER_URL).hostname
×
215

216
const SESSION_DURATION_HOURS = AppConstants.SESSION_DURATION_HOURS || 48
×
217
app.use(session({
×
218
  cookie: {
219
    httpOnly: true,
220
    maxAge: SESSION_DURATION_HOURS * 60 * 60 * 1000, // 48 hours
221
    rolling: true,
222
    sameSite: 'lax',
223
    secure: AppConstants.NODE_ENV !== 'dev'
224
  },
225
  resave: false,
226
  saveUninitialized: true,
227
  secret: AppConstants.COOKIE_SECRET,
228
  store: getRedisStore()
229
}))
230

231
app.use(pickLanguage)
×
232
app.use(addRequestToResponse)
×
233
app.use(recordVisitFromEmail)
×
234

235
app.use('/', DockerflowRoutes)
×
236
app.use('/hibp', HibpRoutes)
×
237
if (AppConstants.FXA_ENABLED) {
×
238
  app.use('/oauth', OAuthRoutes)
×
239
}
240
app.use('/scan', ScanRoutes)
×
241
app.use('/ses', SesRoutes)
×
242
app.use('/user', UserRoutes)
×
243
if (devOrHeroku) app.use('/email-l10n', EmailL10nRoutes)
×
244
app.use('/breach-details', BreachRoutes)
×
245
app.use('/', HomeRoutes)
×
246

247
app.use(Sentry.Handlers.errorHandler())
×
248
app.use(logErrors)
×
249
app.use(localizeErrorMessages)
×
250
app.use(clientErrorHandler)
×
251
app.use(errorHandler)
×
252

253
EmailUtils.init().then(() => {
×
254
  const listener = app.listen(AppConstants.PORT, () => {
×
255
    log.info('Listening', `port ${listener.address().port}`)
×
256
  })
257
}).catch(error => {
258
  log.error('try-initialize-email-error', { error })
×
259
})
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc