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

mozilla / blurts-server / #13220

pending completion
#13220

push

circleci

jswinarton
Monitor premium scaffold

282 of 1633 branches covered (17.27%)

Branch coverage included in aggregate %.

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

959 of 4394 relevant lines covered (21.83%)

1.82 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/emails/email-2022.js'
17
import {
18
  signupReportEmailPartial
19
} from '../views/emails/email-signup-report.js'
20

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

29
const {
30
  FXA_SUBSCRIPTION_PLAN_ID,
31
  FXA_SUBSCRIPTION_PRODUCT_ID,
32
  FXA_SUBSCRIPTION_URL,
33
  SERVER_URL
34
} = AppConstants
×
35

36

37
const log = mozlog('controllers.auth')
×
38

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

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

53
  for (const param of fxaParams.searchParams.keys()) {
×
54
    url.searchParams.append(param, fxaParams.searchParams.get(param))
×
55
  }
56

57
  res.redirect(url)
×
58
}
59

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

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

71
  const fxaUser = await client.code.getToken(req.originalUrl, {
×
72
    state: req.session.state
73
  })
74
  // Clear the session.state to clean up and avoid any replays
75
  req.session.state = null
×
76
  log.debug('fxa-confirmed-fxaUser', fxaUser)
×
77

78
  // TODO is it necessary to check for this here? I'm not sure it is.
79
  const fxaProfileDataRaw = await getProfileData(fxaUser.accessToken)
×
80
  const fxaProfileData = JSON.parse(fxaProfileDataRaw)
×
81
  log.debug('fxa-confirmed-profile-data', fxaProfileData)
×
82
  const email = fxaProfileData.email
×
83
  // Should be a constant not hardcoded
84
  const hasSub = (
85
    fxaProfileData.subscriptions &&
×
86
    fxaProfileData.subscriptions.includes('monitor')
87
  )
88
  log.debug('fxa-confirmed-has-sub', hasSub)
×
89

90
  const existingUser = await getSubscriberByEmail(email)
×
91
  req.session.user = existingUser
×
92

93
  const returnURL = new URL('user/breaches', SERVER_URL)
×
94
  const originalURL = new URL(req.originalUrl, SERVER_URL)
×
95

96
  for (const [key, value] of originalURL.searchParams.entries()) {
×
97
    if (key.startsWith('utm_')) returnURL.searchParams.append(key, value)
×
98
  }
99

100
  // Check if user is signing up or signing in,
101
  // then add new users to db and send email.
102
  if (!existingUser) {
×
103
    // req.session.newUser determines whether or not we show `fxa_new_user_bar`
104
    // in template
105
    req.session.newUser = true
×
106
    const signupLanguage = req.locale
×
107
    const verifiedSubscriber = await addSubscriber(
×
108
      email,
109
      signupLanguage,
110
      fxaUser.accessToken,
111
      fxaUser.refreshToken,
112
      fxaProfileDataRaw
113
    )
114

115
    // Get breaches for email the user signed-up with
116
    const allBreaches = req.app.locals.breaches
×
117
    const unsafeBreachesForEmail = await getBreachesForEmail(
×
118
      getSha1(email),
119
      allBreaches,
120
      true
121
    )
122

123
    // Send report email
124
    const utmCampaignId = 'report'
×
125
    const subject = unsafeBreachesForEmail?.length
×
126
      ? getMessage('email-subject-found-breaches')
127
      : getMessage('email-subject-no-breaches')
128

129
    const data = {
×
130
      breachedEmail: email,
131
      breachLogos: req.app.locals.breachLogoMap,
132
      ctaHref: getEmailCtaHref(utmCampaignId, 'dashboard-cta'),
133
      heading: getMessage('email-breach-summary'),
134
      recipientEmail: email,
135
      subscriberId: verifiedSubscriber,
136
      unsafeBreachesForEmail,
137
      utmCampaign: utmCampaignId
138
    }
139
    const emailTemplate = getTemplate(data, signupReportEmailPartial)
×
140

141
    await sendEmail(data.recipientEmail, subject, emailTemplate)
×
142

143
    req.session.user = verifiedSubscriber
×
144

145
    return res.redirect(returnURL.pathname + returnURL.search)
×
146
  }
147
  // Update existing user's FxA data
148
  const { accessToken, refreshToken } = fxaUser
×
149
  await updateFxAData(existingUser, accessToken, refreshToken, fxaProfileDataRaw)
×
150

151
  res.redirect(returnURL.pathname + returnURL.search)
×
152
}
153

154
/**
155
 * Controller to trigger a logout for user
156
 *
157
 * @param {object} req Contains session.user
158
 * @param {object} res Redirects to homepage
159
 */
160
async function logout (req, res) {
161
  const subscriber = req.session?.user
×
162
  log.info('logout', subscriber?.primary_email)
×
163

164
  // delete oauth session info in database
165
  await removeFxAData(subscriber)
×
166

167
  // clear session cache
168
  req.session.destroy(s => {
×
169
    delete req.session
×
170
    res.redirect('/')
×
171
  })
172
}
173

174

175
/**
176
 * TODO docstring
177
 *
178
 * @param {object} req The express request object
179
 * @param {object} res The express response object
180
 */
181
async function premiumUpgrade (req, res) {
182
  if (isSubscribed(req.user)) {
×
183
    // TODO is this the right redirect?
184
    res.redirect("/user/breaches")
×
185
  }
186

187
  const subscribeUrl = `${FXA_SUBSCRIPTION_URL}/${FXA_SUBSCRIPTION_PRODUCT_ID}?plan=${FXA_SUBSCRIPTION_PLAN_ID}`
×
188
  console.log(subscribeUrl)
×
189

190
  res.redirect(subscribeUrl)
×
191
}
192

193
/**
194
 * TODO docstring
195
 *
196
 * @param {object} req The express request object
197
 * @param {object} res The express response object
198
 */
199
async function premiumConfirmed (req, res) {
200
  // TODO confirm that the subscription now exists on the FxA user and update
201
  // the database
202
  // TODO is this the right redirect?
203
  res.redirect("/user/breaches")
×
204
}
205

206
export { init, confirmed, logout, premiumUpgrade, premiumConfirmed }
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