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

mozilla / blurts-server / aa106bfb-f864-4e55-aa65-a3e84068e55a

pending completion
aa106bfb-f864-4e55-aa65-a3e84068e55a

push

circleci

GitHub
Merge pull request #2755 from mozilla/MNTOR-1046-User-menu

282 of 1172 branches covered (24.06%)

Branch coverage included in aggregate %.

27 of 27 new or added lines in 4 files covered. (100.0%)

959 of 3117 relevant lines covered (30.77%)

4.99 hits per line

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

0.0
/src/app.js
1
/* This Source Code Form is subject to the terms of the Mozilla Public
2
 * License, v. 2.0. If a copy of the MPL was not distributed with this
3
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
4

5
import express from 'express'
6
import session from 'express-session'
7
import connectRedis from 'connect-redis'
8
import helmet from 'helmet'
9
import accepts from 'accepts'
10
import redis from 'redis'
11
import cookieParser from 'cookie-parser'
12

13
import AppConstants from './app-constants.js'
14
import { localStorage } from './utils/local-storage.js'
15
import { errorHandler } from './middleware/error.js'
16
import { doubleCsrfProtection } from './utils/csrf.js'
17
import { initFluentBundles, updateLocale } from './utils/fluent.js'
18
import { loadBreachesIntoApp } from './utils/hibp.js'
19
import indexRouter from './routes/index.js'
20

21
const app = express()
×
22
const isDev = AppConstants.NODE_ENV === 'dev'
×
23

24
// Determine from where to serve client code/assets:
25
// Build script is triggered for `npm start` and assets are served from /dist.
26
// Build script is NOT run for `npm run dev`, assets are served from /src, and nodemon restarts server without build (faster dev).
27
const staticPath = process.env.npm_lifecycle_event === 'start' ? '../dist' : './client'
×
28

29
await initFluentBundles()
×
30

31
async function getRedisStore () {
32
  const RedisStoreConstructor = connectRedis(session)
×
33
  if (['', 'redis-mock'].includes(AppConstants.REDIS_URL)) {
×
34
    const redisMock = await import('redis-mock') // for devs without local redis
×
35
    return new RedisStoreConstructor({ client: redisMock.default.createClient() })
×
36
  }
37
  return new RedisStoreConstructor({ client: redis.createClient({ url: AppConstants.REDIS_URL }) })
×
38
}
39

40
// middleware
41
app.use(
×
42
  helmet({
43
    crossOriginEmbedderPolicy: false
44
  })
45
)
46

47
const imgSrc = [
×
48
  "'self'"
49
]
50

51
if (AppConstants.FXA_ENABLED) {
×
52
  const fxaSrc = new URL(AppConstants.OAUTH_PROFILE_URI).origin
×
53
  imgSrc.push(fxaSrc)
×
54
}
55

56
// disable forced https to allow localhost on Safari
57
app.use(
×
58
  helmet.contentSecurityPolicy({
59
    directives: {
60
      imgSrc,
61
      upgradeInsecureRequests: isDev ? null : []
×
62
    }
63
  })
64
)
65

66
// fallback to default 'no-referrer' only when 'strict-origin-when-cross-origin' not available
67
app.use(
×
68
  helmet.referrerPolicy({
69
    policy: ['no-referrer', 'strict-origin-when-cross-origin']
70
  })
71
)
72

73
// When a text/html request is received, negotiate and store the requested language
74
// Using asyncLocalStorage avoids having to pass req context down through every function (e.g. getMessage())
75
app.use((req, res, next) => {
×
76
  if (!req.headers.accept?.startsWith('text/html')) return next()
×
77

78
  localStorage.run(new Map(), () => {
×
79
    req.locale = updateLocale(accepts(req).languages())
×
80
    localStorage.getStore().set('locale', req.locale)
×
81
    next()
×
82
  })
83
})
84

85
// MNTOR-1009:
86
// Because of heroku's proxy settings, request / cookies are not persisted between calls
87
// Setting the trust proxy to high and securing the cookie allowed the cookie to persist
88
// If cookie.secure is set as true, for nodejs behind proxy, "trust proxy" needs to be set
89
if (AppConstants.NODE_ENV === 'heroku') {
×
90
  app.set('trust proxy', 1)
×
91
}
92

93
// session
94
const SESSION_DURATION_HOURS = AppConstants.SESSION_DURATION_HOURS || 48
×
95
app.use(session({
×
96
  cookie: {
97
    maxAge: SESSION_DURATION_HOURS * 60 * 60 * 1000, // 48 hours
98
    rolling: true,
99
    sameSite: 'lax',
100
    secure: !isDev
101
  },
102
  resave: false,
103
  saveUninitialized: true,
104
  secret: AppConstants.COOKIE_SECRET,
105
  store: await getRedisStore()
106
}))
107

108
// Load breaches into namespaced cache
109
try {
×
110
  await loadBreachesIntoApp(app)
×
111
} catch (error) {
112
  console.error('Error loading breaches into app.locals', error)
×
113
}
114

115
app.use(express.static(staticPath))
×
116
app.use(express.json())
×
117
app.use(cookieParser(AppConstants.COOKIE_SECRET))
×
118
app.use(doubleCsrfProtection)
×
119

120
// routing
121
app.use('/', indexRouter)
×
122
app.use(errorHandler)
×
123

124
// start server
125
app.listen(AppConstants.PORT, function () {
×
126
  console.log(`MONITOR V2: Server listening at ${this.address().port}`)
×
127
  console.log(`Static files served from ${staticPath}`)
×
128
})
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