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

mozilla / blurts-server / #12870

pending completion
#12870

push

circleci

Vinnl
Don't pass unsanitised user input into HTML

It should now no longer be possible to pass arbitrary HTML (e.g.
including malicious JS) into the query parameters and have that
show up on the unsubscribe page. Additionally, the query parameters
get validated, and only the relevant parameters are sent to the
back-end.

282 of 1473 branches covered (19.14%)

Branch coverage included in aggregate %.

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

959 of 4003 relevant lines covered (23.96%)

1.99 hits per line

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

0.0
/src/client/js/partials/unsubscribe.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
const unsubscribePartial = document.querySelector('[data-partial="unsubscribe"]')
×
6

7
function init () {
8
  unsubscribePartial.querySelector('.js-unsubscribe-button').addEventListener('click', handleEvent)
×
9
}
10

11
async function handleEvent (event) {
12
  // TODO: Use more specific and localised messages when we reinstate
13
  // unsubscribing from all emails.
14
  const errorMessage = 'Unsubscribing failed.'
×
15
  const successMessage = 'Unsubscribed successfully.'
×
16

17
  try {
×
18
    const target = event.target
×
19
    const csrfToken = target.getAttribute('data-csrf-token')
×
20
    const unsubscribeParameters = getUnsubscribeParameters()
×
21

22
    if (unsubscribeParameters === null) {
×
23
      const missingParametersToast = document.createElement('toast-alert')
×
24
      missingParametersToast.textContent = errorMessage
×
25
      document.body.append(missingParametersToast)
×
26
      return
×
27
    }
28

29
    const response = await fetch('/user/unsubscribe', {
×
30
      headers: {
31
        'Content-Type': 'application/json',
32
        'x-csrf-token': csrfToken
33
      },
34
      mode: 'same-origin',
35
      method: 'POST',
36
      body: JSON.stringify(unsubscribeParameters)
37
    })
38

39
    if (response?.redirected) {
×
40
      throw response.error
×
41
    }
42

43
    let toast
44
    if (!response.ok) {
×
45
      toast = document.createElement('toast-alert')
×
46
      toast.textContent = errorMessage
×
47
    } else {
48
      toast = document.createElement('toast-alert')
×
49
      toast.setAttribute('type', 'success')
×
50
      toast.textContent = successMessage
×
51
    }
52

53
    document.body.append(toast)
×
54
  } catch (error) {
55
    throw new Error(errorMessage)
×
56
  }
57
}
58

59
/**
60
 * @returns { null | { hash: string; token: string; } }
61
 */
62
function getUnsubscribeParameters () {
63
  const queryParams = new URLSearchParams(document.location.search)
×
64
  const token = queryParams.get('token')
×
65
  const hash = queryParams.get('hash')
×
66
  if (typeof token === 'string' && typeof hash === 'string') {
×
67
    return { hash, token }
×
68
  }
69
  return null
×
70
}
71

72
if (unsubscribePartial) init()
×
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