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

mozilla / blurts-server / 645d0d8f-c92f-45a6-9bcb-96ac0ee11982

pending completion
645d0d8f-c92f-45a6-9bcb-96ac0ee11982

Pull #2980

circleci

GitHub
Update src/controllers/fxaRpEvents.js
Pull Request #2980: MNTOR-981: Fxa Events

282 of 1683 branches covered (16.76%)

Branch coverage included in aggregate %.

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

959 of 4577 relevant lines covered (20.95%)

3.49 hits per line

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

0.0
/src/db/tables/subscribers.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 { destroyOAuthToken } from '../../utils/fxa.js'
6
import Knex from 'knex'
7
import knexConfig from '../knexfile.js'
8
import AppConstants from '../../appConstants.js'
9
import mozlog from '../../utils/log.js'
10
const knex = Knex(knexConfig)
×
11
const { DELETE_UNVERIFIED_SUBSCRIBERS_TIMER } = AppConstants
×
12
const log = mozlog('DB.subscribers')
×
13

14
async function getSubscriberByToken (token) {
15
  const res = await knex('subscribers')
×
16
    .where('primary_verification_token', '=', token)
17

18
  return res[0]
×
19
}
20

21
async function getSubscriberByTokenAndHash (token, emailSha1) {
22
  const res = await knex.table('subscribers')
×
23
    .first()
24
    .where({
25
      primary_verification_token: token,
26
      primary_sha1: emailSha1
27
    })
28
  return res
×
29
}
30

31
async function getSubscribersByHashes (hashes) {
32
  return await knex('subscribers').whereIn('primary_sha1', hashes).andWhere('primary_verified', '=', true)
×
33
}
34

35
async function getSubscriberById (id) {
36
  const [subscriber] = await knex('subscribers').where({
×
37
    id
38
  })
39
  const subscriberAndEmails = await joinEmailAddressesToSubscriber(subscriber)
×
40
  return subscriberAndEmails
×
41
}
42

43
async function getSubscriberByFxaUid (uid) {
44
  const [subscriber] = await knex('subscribers').where({
×
45
    fxa_uid: uid
46
  })
47
  const subscriberAndEmails = await joinEmailAddressesToSubscriber(subscriber)
×
48
  return subscriberAndEmails
×
49
}
50

51
async function getSubscriberByEmail (email) {
52
  const [subscriber] = await knex('subscribers').where({
×
53
    primary_email: email,
54
    primary_verified: true
55
  })
56
  const subscriberAndEmails = await joinEmailAddressesToSubscriber(subscriber)
×
57
  return subscriberAndEmails
×
58
}
59
/**
60
 * Update fxa_refresh_token and fxa_profile_json for subscriber
61
 *
62
 * @param {object} subscriber knex object in DB
63
 * @param {string} fxaAccessToken from Firefox Account Oauth
64
 * @param {string} fxaRefreshToken from Firefox Account Oauth
65
 * @param {string} fxaProfileData from Firefox Account
66
 * @returns {object} updated subscriber knex object in DB
67
 */
68
async function updateFxAData (subscriber, fxaAccessToken, fxaRefreshToken, fxaProfileData) {
69
  const fxaUID = JSON.parse(fxaProfileData).uid
×
70
  const updated = await knex('subscribers')
×
71
    .where('id', '=', subscriber.id)
72
    .update({
73
      fxa_uid: fxaUID,
74
      fxa_access_token: fxaAccessToken,
75
      fxa_refresh_token: fxaRefreshToken,
76
      fxa_profile_json: fxaProfileData
77
    })
78
    .returning('*')
79
  const updatedSubscriber = Array.isArray(updated) ? updated[0] : null
×
80
  if (updatedSubscriber && subscriber.fxa_refresh_token) {
×
81
    destroyOAuthToken({ refresh_token: subscriber.fxa_refresh_token })
×
82
  }
83
  return updatedSubscriber
×
84
}
85

86
/**
87
 * Update fxa_profile_json for subscriber
88
 *
89
 * @param {object} subscriber knex object in DB
90
 * @param {string} fxaProfileData from Firefox Account
91
 * @returns {object} updated subscriber knex object in DB
92
 */
93
async function updateFxAProfileData (subscriber, fxaProfileData) {
94
  await knex('subscribers').where('id', subscriber.id)
×
95
    .update({
96
      fxa_profile_json: fxaProfileData
97
    })
98
  return getSubscriberById(subscriber.id)
×
99
}
100

101
/**
102
 * Remove fxa tokens and profile data for subscriber
103
 *
104
 * @param {object} subscriber knex object in DB
105
 * @returns {object} updated subscriber knex object in DB
106
 */
107
async function removeFxAData (subscriber) {
108
  log.debug('removeFxAData', subscriber)
×
109
  const updated = await knex('subscribers')
×
110
    .where('id', '=', subscriber.id)
111
    .update({
112
      fxa_access_token: null,
113
      fxa_refresh_token: null,
114
      fxa_profile_json: null
115
    })
116
    .returning('*')
117
  const updatedSubscriber = Array.isArray(updated) ? updated[0] : null
×
118
  if (updatedSubscriber && subscriber.fxa_refresh_token) {
×
119
    await destroyOAuthToken({ refresh_token: subscriber.fxa_refresh_token })
×
120
  }
121
  if (updatedSubscriber && subscriber.fxa_access_token) {
×
122
    await destroyOAuthToken({ token: subscriber.fxa_access_token })
×
123
  }
124
  return updatedSubscriber
×
125
}
126

127
async function setBreachesLastShownNow (subscriber) {
128
  // TODO: turn 2 db queries into a single query (also see #942)
129
  const nowDateTime = new Date()
×
130
  const nowTimeStamp = nowDateTime.toISOString()
×
131
  await knex('subscribers')
×
132
    .where('id', '=', subscriber.id)
133
    .update({
134
      breaches_last_shown: nowTimeStamp
135
    })
136
  return getSubscriberByEmail(subscriber.primary_email)
×
137
}
138

139
async function setAllEmailsToPrimary (subscriber, allEmailsToPrimary) {
140
  const updated = await knex('subscribers')
×
141
    .where('id', subscriber.id)
142
    .update({
143
      all_emails_to_primary: allEmailsToPrimary
144
    })
145
    .returning('*')
146
  const updatedSubscriber = Array.isArray(updated) ? updated[0] : null
×
147
  return updatedSubscriber
×
148
}
149

150
/**
151
 * OBSOLETE, preserved for backwards compatibility
152
 * TODO: Delete after monitor v2, only use setBreachResolution for v2
153
 *
154
 * @param {*} options {user, updatedResolvedBreaches}
155
 * @returns subscriber
156
 */
157
async function setBreachesResolved (options) {
158
  const { user, updatedResolvedBreaches } = options
×
159
  await knex('subscribers')
×
160
    .where('id', user.id)
161
    .update({
162
      breaches_resolved: updatedResolvedBreaches
163
    })
164
  return getSubscriberByEmail(user.primary_email)
×
165
}
166

167
/**
168
 * Set "breach_resolution" column with the latest breach resolution object
169
 * This column is meant to replace "breaches_resolved" column, which was used
170
 * for v1.
171
 *
172
 * @param {object} user user object that contains the id of a user
173
 * @param {object} updatedBreachesResolution {emailId: [{breachId: {isResolved: bool, resolutionsChecked: [BreachType]}}, {}...]}
174
 * @returns subscriber
175
 */
176
async function setBreachResolution (user, updatedBreachesResolution) {
177
  await knex('subscribers')
×
178
    .where('id', user.id)
179
    .update({
180
      breach_resolution: updatedBreachesResolution
181
    })
182
  return getSubscriberByEmail(user.primary_email)
×
183
}
184

185
async function setWaitlistsJoined (options) {
186
  const { user, updatedWaitlistsJoined } = options
×
187
  await knex('subscribers')
×
188
    .where('id', user.id)
189
    .update({
190
      waitlists_joined: updatedWaitlistsJoined
191
    })
192
  return getSubscriberByEmail(user.primary_email)
×
193
}
194

195
async function removeSubscriber (subscriber) {
196
  await knex('email_addresses').where({ subscriber_id: subscriber.id }).del()
×
197
  await knex('subscribers').where({ id: subscriber.id }).del()
×
198
}
199

200
async function removeSubscriberByToken (token, emailSha1) {
201
  const subscriber = await getSubscriberByTokenAndHash(token, emailSha1)
×
202
  if (!subscriber) {
×
203
    return false
×
204
  }
205
  await knex('subscribers')
×
206
    .where({
207
      primary_verification_token: subscriber.primary_verification_token,
208
      primary_sha1: subscriber.primary_sha1
209
    })
210
    .del()
211
  return subscriber
×
212
}
213

214
async function deleteUnverifiedSubscribers () {
215
  const expiredDateTime = new Date(Date.now() - DELETE_UNVERIFIED_SUBSCRIBERS_TIMER * 1000)
×
216
  const expiredTimeStamp = expiredDateTime.toISOString()
×
217
  const numDeleted = await knex('subscribers')
×
218
    .where('primary_verified', false)
219
    .andWhere('created_at', '<', expiredTimeStamp)
220
    .del()
221
  log.info('deleteUnverifiedSubscribers', { msg: `Deleted ${numDeleted} rows.` })
×
222
}
223
/**
224
 * Delete subscriber when a FxA user id is provided
225
 * Also deletes all the additional email addresses associated with the account
226
 *
227
 * @param {string} fxaUID FxA user ID
228
 */
229
async function deleteSubscriberByFxAUID (fxaUID) {
230
  const subscriberId = await knex('subscribers').where('fxa_uid', fxaUID).del().returning('id')
×
231
  await knex('email_addresses').where({ subscriber_id: subscriberId }).del()
×
232
}
233

234
async function deleteResolutionsWithEmail (id, email) {
235
  const [subscriber] = await knex('subscribers').where({
×
236
    id
237
  })
238
  const { breach_resolution: breachResolution } = subscriber
×
239
  // if email exists in breach resolution, remove it
240
  if (breachResolution && breachResolution[email]) {
×
241
    delete breachResolution[email]
×
242
    console.info(`Deleting resolution with email: ${email}`)
×
243
    return await setBreachResolution(subscriber, breachResolution)
×
244
  }
245
  console.info(`No resolution with ${email} found, skip`)
×
246
}
247

248
async function updateBreachStats (id, stats) {
249
  await knex('subscribers')
×
250
    .where('id', id)
251
    .update({
252
      breach_stats: stats
253
    })
254
}
255

256
async function updateMonthlyEmailTimestamp (email) {
257
  const res = await knex('subscribers').update({ monthly_email_at: 'now' })
×
258
    .where('primary_email', email)
259
    .returning('monthly_email_at')
260

261
  return res
×
262
}
263

264
/**
265
 * Unsubscribe user from monthly unresolved breach emails
266
 *
267
 * @param {string} token User verification token
268
 */
269
async function updateMonthlyEmailOptout (token) {
270
  await knex('subscribers')
×
271
    .update('monthly_email_optout', true)
272
    .where('primary_verification_token', token)
273
}
274

275
function getSubscribersWithUnresolvedBreachesQuery () {
276
  return knex('subscribers')
×
277
    .whereRaw('monthly_email_optout IS NOT TRUE')
278
    .whereRaw("greatest(created_at, monthly_email_at) < (now() - interval '30 days')")
279
    .whereRaw("(breach_stats #>> '{numBreaches, numUnresolved}')::int > 0")
280
}
281

282
async function getSubscribersWithUnresolvedBreaches (limit = 0) {
×
283
  let query = getSubscribersWithUnresolvedBreachesQuery()
×
284
    .select('primary_email', 'primary_verification_token', 'breach_stats', 'signup_language')
285
  if (limit) {
×
286
    query = query.limit(limit).orderBy('created_at')
×
287
  }
288
  return await query
×
289
}
290

291
async function getSubscribersWithUnresolvedBreachesCount () {
292
  const query = getSubscribersWithUnresolvedBreachesQuery()
×
293
  const count = parseInt((await query.count({ count: '*' }))[0].count)
×
294
  return count
×
295
}
296

297
/** Private */
298

299
async function joinEmailAddressesToSubscriber (subscriber) {
300
  if (subscriber) {
×
301
    const emailAddressRecords = await knex('email_addresses').where({
×
302
      subscriber_id: subscriber.id
303
    })
304
    subscriber.email_addresses = emailAddressRecords.map(
×
305
      emailAddress => ({ id: emailAddress.id, email: emailAddress.email })
×
306
    )
307
  }
308
  return subscriber
×
309
}
310
export {
311
  getSubscriberByToken,
312
  getSubscribersByHashes,
313
  getSubscriberByTokenAndHash,
314
  getSubscriberById,
315
  getSubscriberByFxaUid,
316
  getSubscriberByEmail,
317
  getSubscribersWithUnresolvedBreachesQuery,
318
  getSubscribersWithUnresolvedBreaches,
319
  getSubscribersWithUnresolvedBreachesCount,
320
  updateFxAData,
321
  removeFxAData,
322
  updateFxAProfileData,
323
  setBreachesLastShownNow,
324
  setAllEmailsToPrimary,
325
  setBreachesResolved,
326
  setBreachResolution,
327
  setWaitlistsJoined,
328
  updateBreachStats,
329
  updateMonthlyEmailTimestamp,
330
  updateMonthlyEmailOptout,
331
  removeSubscriber,
332
  removeSubscriberByToken,
333
  deleteUnverifiedSubscribers,
334
  deleteSubscriberByFxAUID,
335
  deleteResolutionsWithEmail
336
}
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