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

mozilla / blurts-server / 1b23622c-63be-46b0-9771-9c2e695142f6

pending completion
1b23622c-63be-46b0-9771-9c2e695142f6

push

circleci

GitHub
Merge pull request #2802 from mozilla/MNTOR-1154-Fix-monitored-emails-limit

282 of 1281 branches covered (22.01%)

Branch coverage included in aggregate %.

1 of 1 new or added line in 1 file covered. (100.0%)

959 of 3457 relevant lines covered (27.74%)

4.5 hits per line

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

0.0
/src/controllers/settings.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 AppConstants from '../app-constants.js'
6

7
import {
8
  getUserEmails,
9
  resetUnverifiedEmailAddress,
10
  addSubscriberUnverifiedEmailHash,
11
  removeOneSecondaryEmail,
12
  getEmailById,
13
  verifyEmailHash
14
} from '../db/tables/email_addresses.js'
15

16
import { setAllEmailsToPrimary } from '../db/tables/subscribers.js'
17

18
import { fluentError, getMessage } from '../utils/fluent.js'
19
import { sendEmail, getVerificationUrl, getUnsubscribeUrl } from '../utils/email.js'
20

21
import { getBreachesForEmail } from '../utils/hibp.js'
22
import { generateToken } from '../utils/csrf.js'
23

24
import { mainLayout } from '../views/main.js'
25
import { settings } from '../views/partials/settings.js'
26
import { getTemplate } from '../views/email-2022.js'
27
import { verifyPartial } from '../views/partials/email-verify.js'
28

29
async function settingsPage (req, res) {
30
  const emails = await getUserEmails(req.session.user.id)
×
31
  // Add primary subscriber email to the list
32
  emails.push({
×
33
    email: req.session.user.primary_email,
34
    sha1: req.session.user.primary_sha1,
35
    primary: true,
36
    verified: true
37
  })
38

39
  const breachCounts = new Map()
×
40

41
  const allBreaches = req.app.locals.breaches
×
42
  for (const email of emails) {
×
43
    const breaches = await getBreachesForEmail(
×
44
      email.sha1,
45
      allBreaches,
46
      true,
47
      false
48
    )
49
    breachCounts.set(email.email, breaches?.length || 0)
×
50
  }
51

52
  const {
53
    all_emails_to_primary: allEmailsToPrimary,
54
    fxa_profile_json: fxaProfile
55
  } = req.user
×
56

57
  const data = {
×
58
    allEmailsToPrimary,
59
    fxaProfile,
60
    partial: settings,
61
    emails,
62
    breachCounts,
63
    limit: AppConstants.MAX_NUM_ADDRESSES,
64
    csrfToken: generateToken(res)
65
  }
66

67
  res.send(mainLayout(data))
×
68
}
69

70
async function addEmail (req, res) {
71
  const sessionUser = req.user
×
72
  const email = req.body.email
×
73
  // Use the same regex as HTML5 email input type
74
  // https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/email#basic_validation
75
  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])?)*$/
×
76

77
  if (!email || !emailRegex.test(email)) {
×
78
    throw fluentError('user-add-invalid-email')
×
79
  }
80

81
  // Total max number of email addresses minus one to account for the primary email
82
  if (sessionUser.email_addresses.length >= AppConstants.MAX_NUM_ADDRESSES - 1) {
×
83
    throw fluentError('user-add-too-many-emails')
×
84
  }
85

86
  checkForDuplicateEmail(sessionUser, email)
×
87

88
  const unverifiedSubscriber = await addSubscriberUnverifiedEmailHash(
×
89
    req.session.user,
90
    email
91
  )
92

93
  await sendVerificationEmail(unverifiedSubscriber.id)
×
94

95
  return res.json({
×
96
    success: true,
97
    status: 200,
98
    message: 'Sent the verification email'
99
  })
100
}
101

102
function checkForDuplicateEmail (sessionUser, email) {
103
  const emailLowerCase = email.toLowerCase()
×
104
  if (emailLowerCase === sessionUser.primary_email.toLowerCase()) {
×
105
    throw fluentError('user-add-duplicate-email')
×
106
  }
107

108
  for (const secondaryEmail of sessionUser.email_addresses) {
×
109
    if (emailLowerCase === secondaryEmail.email.toLowerCase()) {
×
110
      throw fluentError('user-add-duplicate-email')
×
111
    }
112
  }
113
}
114

115
async function removeEmail (req, res) {
116
  const emailId = req.body.emailId
×
117
  const sessionUser = req.user
×
118
  const existingEmail = await getEmailById(emailId)
×
119

120
  if (existingEmail.subscriber_id !== sessionUser.id) {
×
121
    throw fluentError('error-not-subscribed')
×
122
  }
123

124
  removeOneSecondaryEmail(emailId)
×
125
  res.redirect('/user/settings')
×
126
}
127

128
async function resendEmail (req, res) {
129
  const emailId = req.body.emailId
×
130
  const sessionUser = req.user
×
131
  const existingEmail = await getUserEmails(sessionUser.id)
×
132

133
  const filteredEmail = existingEmail.filter(
×
134
    (a) => a.email === emailId && a.subscriber_id === sessionUser.id
×
135
  )
136

137
  if (!filteredEmail) {
×
138
    throw fluentError('user-verify-token-error')
×
139
  }
140

141
  await sendVerificationEmail(emailId)
×
142

143
  return res.json({
×
144
    success: true,
145
    status: 200,
146
    message: 'Sent the verification email'
147
  })
148
}
149

150
async function sendVerificationEmail (emailId) {
151
  const unverifiedEmailAddressRecord = await resetUnverifiedEmailAddress(
×
152
    emailId
153
  )
154
  const recipientEmail = unverifiedEmailAddressRecord.email
×
155
  const data = {
×
156
    recipientEmail,
157
    ctaHref: getVerificationUrl(unverifiedEmailAddressRecord),
158
    utmCampaign: 'email_verify',
159
    unsubscribeUrl: getUnsubscribeUrl(
160
      unverifiedEmailAddressRecord,
161
      'account-verification-email'
162
    ),
163
    heading: getMessage('email-verify-heading'),
164
    subheading: getMessage('email-verify-subhead'),
165
    partial: { name: 'verify' }
166
  }
167
  await sendEmail(
×
168
    recipientEmail,
169
    getMessage('email-subject-verify'),
170
    getTemplate(data, verifyPartial(data))
171
  )
172
}
173

174
async function verifyEmail (req, res) {
175
  const token = req.query.token
×
176
  await verifyEmailHash(token)
×
177

178
  return res.redirect('/user/settings')
×
179
}
180

181
async function updateCommunicationOptions (req, res) {
182
  const sessionUser = req.user
×
183
  // 0 = Send breach alerts to the email address found in brew breach.
184
  // 1 = Send all breach alerts to user's primary email address.
185
  const allEmailsToPrimary = Number(req.body.communicationOption) === 1
×
186
  const updatedSubscriber = await setAllEmailsToPrimary(
×
187
    sessionUser,
188
    allEmailsToPrimary
189
  )
190
  req.session.user = updatedSubscriber
×
191

192
  return res.json({
×
193
    success: true,
194
    status: 200,
195
    message: 'Communications options updated'
196
  })
197
}
198

199
export {
200
  settingsPage,
201
  resendEmail,
202
  addEmail,
203
  removeEmail,
204
  verifyEmail,
205
  updateCommunicationOptions
206
}
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