Skip to content

Commit

Permalink
tighten Vapid subject validation (#789)
Browse files Browse the repository at this point in the history
only accept Vapid subjects with https: or mailto: URL protocols, as per spec

add tests for localhost https: and mailto: subjects
  • Loading branch information
delfuego authored Apr 1, 2023
1 parent 0e6cb56 commit 2eb5ea2
Show file tree
Hide file tree
Showing 2 changed files with 38 additions and 13 deletions.
27 changes: 16 additions & 11 deletions src/vapid-helper.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ const crypto = require('crypto');
const urlBase64 = require('urlsafe-base64');
const asn1 = require('asn1.js');
const jws = require('jws');
const url = require('url');
const { URL } = require('url');

const WebPushConstants = require('./web-push-constants.js');

Expand Down Expand Up @@ -71,18 +71,22 @@ function validateSubject(subject) {
}

if (typeof subject !== 'string' || subject.length === 0) {
throw new Error('The subject value must be a string containing a URL or '
throw new Error('The subject value must be a string containing an https: URL or '
+ 'mailto: address. ' + subject);
}

if (subject.indexOf('mailto:') !== 0) {
const subjectParseResult = url.parse(subject);
if (!subjectParseResult.hostname) {
throw new Error('Vapid subject is not a url or mailto url. ' + subject);
} else if (subjectParseResult.hostname === 'localhost' && subjectParseResult.protocol === 'https:') {
console.warn('VAPID subject points to a localhost web URI, which is unsupported by Apple\'s push notification '
+ 'server and will result in a BadJwtToken error when sending notifications.');
try {
const subjectParseResult = new URL(subject);
if (!['https:', 'mailto:'].includes(subjectParseResult.protocol)) {
throw new Error('Vapid subject is not an https: or mailto: URL. ' + subject);
}
if (subjectParseResult.hostname === 'localhost') {
console.warn('Vapid subject points to a localhost web URI, which is unsupported by '
+ 'Apple\'s push notification server and will result in a BadJwtToken error when '
+ 'sending notifications.');
}
} catch (err) {
throw new Error('Vapid subject is not a valid URL. ' + subject);
}
}

Expand Down Expand Up @@ -189,8 +193,9 @@ function getVapidHeaders(audience, subject, publicKey, privateKey, contentEncodi
+ 'origin of a push service. ' + audience);
}

const audienceParseResult = url.parse(audience);
if (!audienceParseResult.hostname) {
try {
new URL(audience); // eslint-disable-line no-new
} catch (err) {
throw new Error('VAPID audience is not a url. ' + audience);
}

Expand Down
24 changes: 22 additions & 2 deletions test/test-vapid-helper.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,12 @@ const webPush = require('../src/index');
const vapidHelper = require('../src/vapid-helper');

const VALID_AUDIENCE = 'https://example.com';
const VALID_SUBJECT_MAILTO = 'mailto: [email protected]';
const VALID_SUBJECT_URL = 'https://exampe.com/contact';
const VALID_SUBJECT_MAILTO = 'mailto:[email protected]';
const VALID_SUBJECT_LOCALHOST_MAILTO = 'mailto:user@localhost';
const VALID_SUBJECT_URL = 'https://example.com/contact';
const WARN_SUBJECT_LOCALHOST_URL = 'https://localhost';
const INVALID_SUBJECT_URL_1 = 'http://example.gov';
const INVALID_SUBJECT_URL_2 = 'ftp://example.net';
const VALID_PUBLIC_KEY = urlBase64.encode(Buffer.alloc(65));
const VALID_UNSAFE_BASE64_PUBLIC_KEY = Buffer.alloc(65).toString('base64');
const VALID_PRIVATE_KEY = urlBase64.encode(Buffer.alloc(32));
Expand Down Expand Up @@ -101,6 +105,14 @@ suite('Test Vapid Helpers', function() {
function() {
vapidHelper.getVapidHeaders('Not a URL', VALID_SUBJECT_MAILTO, VALID_PUBLIC_KEY, VALID_PRIVATE_KEY);
},
function() {
// http URL protocol
vapidHelper.getVapidHeaders(VALID_AUDIENCE, INVALID_SUBJECT_URL_1, VALID_PUBLIC_KEY, VALID_PRIVATE_KEY);
},
function() {
// ftp URL protocol
vapidHelper.getVapidHeaders(VALID_AUDIENCE, INVALID_SUBJECT_URL_2, VALID_PUBLIC_KEY, VALID_PRIVATE_KEY);
},
function() {
vapidHelper.getVapidHeaders(VALID_AUDIENCE, 'Some Random String', VALID_PUBLIC_KEY, VALID_PRIVATE_KEY);
},
Expand Down Expand Up @@ -168,9 +180,17 @@ suite('Test Vapid Helpers', function() {
function(contentEncoding) {
return vapidHelper.getVapidHeaders(VALID_AUDIENCE, VALID_SUBJECT_URL, VALID_PUBLIC_KEY, VALID_PRIVATE_KEY, contentEncoding);
},
function(contentEncoding) {
// localhost https: subject; should pass, since we don't throw an error for this, just warn to console
return vapidHelper.getVapidHeaders(VALID_AUDIENCE, WARN_SUBJECT_LOCALHOST_URL, VALID_PUBLIC_KEY, VALID_PRIVATE_KEY, contentEncoding);
},
function(contentEncoding) {
return vapidHelper.getVapidHeaders(VALID_AUDIENCE, VALID_SUBJECT_MAILTO, VALID_PUBLIC_KEY, VALID_PRIVATE_KEY, contentEncoding);
},
function(contentEncoding) {
// localhost mailto: subject
return vapidHelper.getVapidHeaders(VALID_AUDIENCE, VALID_SUBJECT_LOCALHOST_MAILTO, VALID_PUBLIC_KEY, VALID_PRIVATE_KEY, contentEncoding);
},
function(contentEncoding) {
return vapidHelper.getVapidHeaders(VALID_AUDIENCE, VALID_SUBJECT_URL, VALID_PUBLIC_KEY, VALID_PRIVATE_KEY, contentEncoding, VALID_EXPIRATION);
},
Expand Down

0 comments on commit 2eb5ea2

Please sign in to comment.