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

mozilla / blurts-server / #13221

pending completion
#13221

push

circleci

mansaj
fxa-rp-events handler

282 of 1673 branches covered (16.86%)

Branch coverage included in aggregate %.

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

959 of 4533 relevant lines covered (21.16%)

1.76 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 '../../app-constants.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
 * @param {string} fxaUID FxA user ID
227
 */
228
async function deleteSubscriberByFxAUID (fxaUID) {
229
  const subscriberId = await knex('subscribers').where('fxa_uid', fxaUID).del().returning('id')
×
230
  await knex('email_addresses').where({ subscriber_id: subscriberId }).del()
×
231
}
232

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

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

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

260
  return res
×
261
}
262

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

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

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

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

296
/** Private */
297

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