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

mozilla / blurts-server / 4040bc25-7f30-464b-958e-746a756a4e44

pending completion
4040bc25-7f30-464b-958e-746a756a4e44

push

circleci

GitHub
Merge pull request #2790 from mozilla/MNTOR-1056-Migrate-breach-alert-email

282 of 1375 branches covered (20.51%)

Branch coverage included in aggregate %.

174 of 174 new or added lines in 17 files covered. (100.0%)

959 of 3709 relevant lines covered (25.86%)

4.2 hits per line

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

0.0
/src/controllers/auth.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 { URL } from 'url'
6
import { randomBytes } from 'crypto'
7

8
import AppConstants from '../app-constants.js'
9
import {
10
  getSubscriberByEmail,
11
  removeFxAData,
12
  updateFxAData
13
} from '../db/tables/subscribers.js'
14
import { addSubscriber } from '../db/tables/email_addresses.js'
15

16
import { getTemplate } from '../views/email-2022.js'
17
import {
18
  signupReportEmailPartial
19
} from '../views/partials/email-signup-report.js'
20

21
import { getBreachesForEmail } from '../utils/hibp.js'
22
import { getMessage } from '../utils/fluent.js'
23
import { getProfileData, FxAOAuthClient } from '../utils/fxa.js'
24
import {
25
  getEmailCtaHref,
26
  getUnsubscribeUrl,
27
  sendEmail
28
} from '../utils/email.js'
29
import { UnauthorizedError } from '../utils/error.js'
30
import mozlog from '../utils/log.js'
31

32
const { SERVER_URL } = AppConstants
×
33

34
const log = mozlog('controllers.auth')
×
35

36
function init (req, res, next, client = FxAOAuthClient) {
×
37
  // Set a random state string in a cookie so that we can verify
38
  // the user when they're redirected back to us after auth.
39
  const state = randomBytes(40).toString('hex')
×
40
  req.session.state = state
×
41
  const url = new URL(client.code.getUri({ state }))
×
42
  const fxaParams = new URL(req.url, SERVER_URL)
×
43

44
  req.session.utmContents = {}
×
45
  url.searchParams.append('prompt', 'login')
×
46
  url.searchParams.append('max_age', 0)
×
47
  url.searchParams.append('access_type', 'offline')
×
48
  url.searchParams.append('action', 'email')
×
49

50
  for (const param of fxaParams.searchParams.keys()) {
×
51
    url.searchParams.append(param, fxaParams.searchParams.get(param))
×
52
  }
53

54
  res.redirect(url)
×
55
}
56

57
async function confirmed (req, res, next, client = FxAOAuthClient) {
×
58
  if (!req.session.state) {
×
59
    log.error('oauth-invalid-session', 'req.session.state missing')
×
60
    throw new UnauthorizedError(getMessage('oauth-invalid-session'))
×
61
  }
62

63
  if (req.session.state !== req.query.state) {
×
64
    log.error('oauth-invalid-session', 'req.session does not match req.query')
×
65
    throw new UnauthorizedError(getMessage('oauth-invalid-session'))
×
66
  }
67

68
  const fxaUser = await client.code.getToken(req.originalUrl, {
×
69
    state: req.session.state
70
  })
71
  // Clear the session.state to clean up and avoid any replays
72
  req.session.state = null
×
73
  log.debug('fxa-confirmed-fxaUser', fxaUser)
×
74
  const fxaProfileData = await getProfileData(fxaUser.accessToken)
×
75
  log.debug('fxa-confirmed-profile-data', fxaProfileData)
×
76
  const email = JSON.parse(fxaProfileData).email
×
77

78
  const existingUser = await getSubscriberByEmail(email)
×
79
  req.session.user = existingUser
×
80

81
  const returnURL = new URL('user/breaches', SERVER_URL)
×
82
  const originalURL = new URL(req.originalUrl, SERVER_URL)
×
83

84
  for (const [key, value] of originalURL.searchParams.entries()) {
×
85
    if (key.startsWith('utm_')) returnURL.searchParams.append(key, value)
×
86
  }
87

88
  // Check if user is signing up or signing in,
89
  // then add new users to db and send email.
90
  if (!existingUser) {
×
91
    // req.session.newUser determines whether or not we show `fxa_new_user_bar`
92
    // in template
93
    req.session.newUser = true
×
94
    const signupLanguage = req.locale
×
95
    const verifiedSubscriber = await addSubscriber(
×
96
      email,
97
      signupLanguage,
98
      fxaUser.accessToken,
99
      fxaUser.refreshToken,
100
      fxaProfileData
101
    )
102

103
    // Get breaches for email the user signed-up with
104
    const allBreaches = req.app.locals.breaches
×
105
    const unsafeBreachesForEmail = await getBreachesForEmail(
×
106
      email.sha1,
107
      allBreaches,
108
      true
109
    )
110

111
    // Send report email
112
    const utmCampaignId = 'report'
×
113
    const heading = unsafeBreachesForEmail?.length
×
114
      ? getMessage('email-subject-found-breaches')
115
      : getMessage('email-subject-no-breaches')
116

117
    const data = {
×
118
      breachedEmail: email,
119
      ctaHref: getEmailCtaHref(utmCampaignId, 'dashboard-cta'),
120
      heading,
121
      recipientEmail: email,
122
      subscriberId: verifiedSubscriber,
123
      unsafeBreachesForEmail,
124
      unsubscribeUrl: getUnsubscribeUrl(email, 'account-verification-email'),
125
      utmCampaign: utmCampaignId
126
    }
127
    const emailTemplate = getTemplate(data, signupReportEmailPartial)
×
128
    const subject = getMessage('breach-alert-subject')
×
129

130
    await sendEmail(data.recipientEmail, subject, emailTemplate)
×
131

132
    req.session.user = verifiedSubscriber
×
133

134
    return res.redirect(returnURL.pathname + returnURL.search)
×
135
  }
136
  // Update existing user's FxA data
137
  const { accessToken, refreshToken } = fxaUser
×
138
  await updateFxAData(existingUser, accessToken, refreshToken, fxaProfileData)
×
139

140
  res.redirect(returnURL.pathname + returnURL.search)
×
141
}
142

143
/**
144
 * Controller to trigger a logout for user
145
 * @param {object} req contains session.user
146
 * @param {object} res redirects to homepage
147
 */
148
async function logout (req, res) {
149
  const subscriber = req.session?.user
×
150
  log.info('logout', subscriber?.primary_email)
×
151

152
  // delete oauth session info in database
153
  await removeFxAData(subscriber)
×
154

155
  // clear session cache
156
  req.session.destroy(s => {
×
157
    delete req.session
×
158
    res.redirect('/')
×
159
  })
160
}
161

162
export { init, confirmed, logout }
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