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

mozilla / blurts-server / fd122a42-31ad-4f0c-91a5-2aefaa9be907

pending completion
fd122a42-31ad-4f0c-91a5-2aefaa9be907

push

circleci

GitHub
Merge pull request #2961 from mozilla/MNTOR-1504-Add-links-to-websites

282 of 1768 branches covered (15.95%)

Branch coverage included in aggregate %.

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

959 of 4670 relevant lines covered (20.54%)

3.42 hits per line

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

0.0
/src/utils/breachResolution.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 '../appConstants.js'
6
import { getMessage } from './fluent.js'
7

8
/**
9
 * Equivalent of Typescript "enum"
10
 * These enum types map to HIBP's breach data types, defined in HIBP's API
11
 * Always reference enum instead of strings to avoid spelling error / typos (ie. BreachDataTypes.Passwords)
12
 */
13
const BreachDataTypes = {
×
14
  Passwords: 'passwords',
15
  Email: 'email-addresses',
16
  SSN: 'social-security-numbers',
17
  CreditCard: 'partial-credit-card-data',
18
  BankAccount: 'bank-account-numbers',
19
  PIN: 'pins',
20
  IP: 'ip-addresses',
21
  Address: 'physical-addresses',
22
  DoB: 'dates-of-birth',
23
  Phone: 'phone-numbers',
24
  SecurityQuestions: 'security-questions-and-answers',
25
  HistoricalPasswords: 'historical-passwords',
26
  General: 'general'
27
}
28

29
/**
30
 * TODO: Map from google doc: https://docs.google.com/document/d/1KoItFsTYVIBInIG2YmA7wSxkKS4vti_X0A0td_yaHVM/edit#
31
 * Hardcoded map of breach resolution data types
32
 *
33
 * @type { Record<keyof BreachDataTypes, { priority: number, header: string, body?: string, applicableCountryCodes?: string[] }> }
34
 */
35
const breachResolutionDataTypes = {
×
36
  [BreachDataTypes.Passwords]: {
37
    priority: 1,
38
    header: 'breach-checklist-pw-header-text',
39
    body: 'breach-checklist-pw-body-text'
40
  },
41
  [BreachDataTypes.Email]: {
42
    priority: 2,
43
    header: 'breach-checklist-email-header-2',
44
    body: 'breach-checklist-email-body'
45
  },
46
  [BreachDataTypes.SSN]: {
47
    priority: 3,
48
    header: 'breach-checklist-ssn-header',
49
    body: 'breach-checklist-ssn-body-2',
50
    // The resolution involves American companies, and thus does not apply in other countries:
51
    applicableCountryCodes: ['us']
52
  },
53
  [BreachDataTypes.CreditCard]: {
54
    priority: 4,
55
    header: 'breach-checklist-cc-header',
56
    body: 'breach-checklist-cc-body'
57
  },
58
  [BreachDataTypes.BankAccount]: {
59
    priority: 5,
60
    header: 'breach-checklist-bank-header',
61
    body: 'breach-checklist-bank-body'
62
  },
63
  [BreachDataTypes.PIN]: {
64
    priority: 6,
65
    header: 'breach-checklist-pin-header',
66
    body: 'breach-checklist-pin-body'
67
  },
68
  [BreachDataTypes.IP]: {
69
    priority: 7,
70
    header: 'breach-checklist-ip-header-2',
71
    body: 'breach-checklist-ip-body'
72
  },
73
  [BreachDataTypes.Address]: {
74
    priority: 8,
75
    header: 'breach-checklist-address-header',
76
    body: 'breach-checklist-address-body'
77
  },
78
  [BreachDataTypes.DoB]: {
79
    priority: 9,
80
    header: 'breach-checklist-dob-header',
81
    body: 'breach-checklist-dob-body'
82
  },
83
  [BreachDataTypes.Phone]: {
84
    priority: 10,
85
    header: 'breach-checklist-phone-header-2'
86
  },
87
  [BreachDataTypes.SecurityQuestions]: {
88
    priority: 11,
89
    header: 'breach-checklist-sq-header-text',
90
    body: 'breach-checklist-sq-body-text'
91
  },
92
  [BreachDataTypes.HistoricalPasswords]: {
93
    priority: 12,
94
    header: 'breach-checklist-hp-header',
95
    body: 'breach-checklist-hp-body-2'
96
  },
97
  [BreachDataTypes.General]: {
98
    priority: 13,
99
    header: 'breach-checklist-general-header'
100
  }
101
}
102

103
/**
104
 * Append a field "breachChecklist" to the breaches array of each verified emails
105
 * The checklist serves the UI with relevant recommendations based on the array of datatypes leaked during a breach.
106
 *
107
 * @param {Array} userBreachData contains monitored verified emails array. Each email may contain a breaches array
108
 * @param {Partial<{ countryCode: string }>} options
109
 * @returns {*} void
110
 */
111
function appendBreachResolutionChecklist (userBreachData, options = {}) {
×
112
  const { verifiedEmails } = userBreachData
×
113

114
  for (const { breaches } of verifiedEmails) {
×
115
    breaches.forEach(b => {
×
116
      const dataClasses = b.DataClasses
×
117
      const blockList = (AppConstants.HIBP_BREACH_DOMAIN_BLOCKLIST ?? '').split(',')
×
118
      const showLink = b.Domain && !blockList.includes(b.Domain)
×
119

120
      const args = {
×
121
        companyName: b.Name,
122
        breachedCompanyLink: showLink ? `https://${b.Domain}` : '',
×
123
        firefoxRelayLink: `<a href="https://relay.firefox.com/?utm_medium=mozilla-websites&utm_source=monitor&utm_campaign=&utm_content=breach-resolution" target="_blank">${getMessage('breach-checklist-link-firefox-relay')}</a>`,
124
        passwordManagerLink: `<a href="https://www.mozilla.org/firefox/features/password-manager/?utm_medium=mozilla-websites&utm_source=monitor&utm_campaign=&utm_content=breach-resolution" target="_blank">${getMessage('breach-checklist-link-password-manager')}</a>`,
125
        mozillaVpnLink: `<a href="https://www.mozilla.org/products/vpn/?utm_medium=mozilla-websites&utm_source=monitor&utm_campaign=&utm_content=breach-resolution" target="_blank">${getMessage('breach-checklist-link-mozilla-vpn')}</a>`,
126
        equifaxLink: '<a href="https://www.equifax.com/personal/credit-report-services/credit-freeze/" target="_blank">Equifax</a>',
127
        experianLink: '<a href="https://www.experian.com/freeze/center.html" target="_blank">Experian</a>',
128
        transUnionLink: '<a href="https://www.transunion.com/credit-freeze" target="_blank">TransUnion</a>'
129
      }
130
      b.breachChecklist = getResolutionRecsPerBreach(dataClasses, args, options)
×
131
    })
132
  }
133
}
134

135
/**
136
 * Get a subset of the breach resolution data types map
137
 * based on the array of datatypes leaked during a breach
138
 *
139
 * @param {Array} dataTypes datatypes leaked during the breach
140
 * @param {object} args contains necessary variables for the fluent file
141
 *  - companyName
142
 *  - breachedCompanyUrl
143
 * @param {Partial<{ countryCode: string }>} options
144
 * @returns {Map} map of relevant breach resolution recommendations
145
 */
146
function getResolutionRecsPerBreach (dataTypes, args, options = {}) {
×
147
  const filteredBreachRecs = {}
×
148

149
  // filter breachResolutionDataTypes based on relevant data types passed in
150
  for (const resolution of Object.entries(breachResolutionDataTypes)) {
×
151
    const [key, value] = resolution
×
152
    if (
×
153
      dataTypes.includes(key) &&
×
154
      // Hide resolutions that apply to other countries than the user's:
155
      (!options.countryCode || !Array.isArray(value.applicableCountryCodes) || value.applicableCountryCodes.includes(options.countryCode.toLowerCase()))
156
    ) {
157
      filteredBreachRecs[key] = getRecommendationFromResolution(resolution, args)
×
158
    }
159
  }
160

161
  // If we did not have any recommendations, add a generic recommendation:
162
  if (Object.keys(filteredBreachRecs).length === 0) {
×
163
    const resolutionTypeGeneral = BreachDataTypes.General
×
164
    filteredBreachRecs[resolutionTypeGeneral] = getRecommendationFromResolution(
×
165
      [
166
        resolutionTypeGeneral,
167
        breachResolutionDataTypes[resolutionTypeGeneral]
168
      ],
169
      args
170
    )
171
  }
172

173
  // loop through the breach recs
174
  return filteredBreachRecs
×
175
}
176

177
/**
178
 * Get the fluent string for the body
179
 *
180
 * @param {string} body for the fluent body string
181
 * @param {object} args
182
 * @returns {string} body string
183
 */
184
function getBodyMessage (body, args) {
185
  const { stringArgs } = args
×
186

187
  const companyLink = stringArgs.breachedCompanyLink
×
188
  return getMessage(body, stringArgs)
×
189
    .replace(
190
      '<breached-company-link>',
191
      companyLink ? `<a href="${companyLink}" target="_blank">` : ''
×
192
    )
193
    .replace(
194
      '</breached-company-link>',
195
      companyLink ? '</a>' : ''
×
196
    )
197
}
198

199
// find fluent text based on fluent ids
200
function getRecommendationFromResolution (resolution, args) {
201
  const [resolutionType, resolutionContent] = resolution
×
202
  let { header, body, priority } = resolutionContent
×
203

204
  header = header ? getMessage(header, args) : ''
×
205
  body = body
×
206
    ? getBodyMessage(body, { resolutionType, stringArgs: args })
207
    : ''
208
  return { header, body, priority }
×
209
}
210

211
/**
212
 * Take breach DataTypes array from HIBP and filter based on BreachDataTypes enums above
213
 *
214
 * @param {Array} originalDataTypes breach DataTypes array from HIBP
215
 * @returns {Array} filtered breach data types
216
 */
217
function filterBreachDataTypes (originalDataTypes) {
218
  const relevantDataTypes = Object.values(BreachDataTypes)
×
219
  return originalDataTypes.filter(d => relevantDataTypes.includes(d))
×
220
}
221

222
export { BreachDataTypes, appendBreachResolutionChecklist, filterBreachDataTypes }
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