Skip to content

Commit ab8d884

Browse files
authored
Merge pull request #220 from gauntface/vapid-string
Changing vapid input / output to be base64url encoded string
2 parents 251c28d + 616c2b9 commit ab8d884

File tree

5 files changed

+194
-46
lines changed

5 files changed

+194
-46
lines changed

src/vapid-helper.js

Lines changed: 59 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -32,38 +32,12 @@ function generateVAPIDKeys() {
3232
curve.generateKeys();
3333

3434
return {
35-
publicKey: curve.getPublicKey(),
36-
privateKey: curve.getPrivateKey()
35+
publicKey: urlBase64.encode(curve.getPublicKey()),
36+
privateKey: urlBase64.encode(curve.getPrivateKey())
3737
};
3838
}
3939

40-
/**
41-
* This method takes the required VAPID parameters and returns the required
42-
* header to be added to a Web Push Protocol Request.
43-
* @param {string} audience This must be the origin of the push service.
44-
* @param {string} subject This should be a URL or a 'mailto:' email
45-
* address.
46-
* @param {Buffer} publicKey The VAPID public key.
47-
* @param {Buffer} privateKey The VAPID private key.
48-
* @param {integer} [expiration] The expiration of the VAPID JWT.
49-
* @return {Object} Returns an Object with the Authorization and
50-
* 'Crypto-Key' values to be used as headers.
51-
*/
52-
function getVapidHeaders(audience, subject, publicKey, privateKey, expiration) {
53-
if (!audience) {
54-
throw new Error('No audience set in vapid.audience.');
55-
}
56-
57-
if (typeof audience !== 'string' || audience.length === 0) {
58-
throw new Error('The audience value must be a string containing the ' +
59-
'origin of a push service. ' + audience);
60-
}
61-
62-
const audienceParseResult = url.parse(audience);
63-
if (!audienceParseResult.hostname) {
64-
throw new Error('VAPID audience is not a url. ' + audience);
65-
}
66-
40+
function validateSubject(subject) {
6741
if (!subject) {
6842
throw new Error('No subject set in vapid.subject.');
6943
}
@@ -79,30 +53,76 @@ function getVapidHeaders(audience, subject, publicKey, privateKey, expiration) {
7953
throw new Error('Vapid subject is not a url or mailto url. ' + subject);
8054
}
8155
}
56+
}
8257

58+
function validatePublicKey(publicKey) {
8359
if (!publicKey) {
8460
throw new Error('No key set vapid.publicKey');
8561
}
8662

87-
if (!(publicKey instanceof Buffer)) {
88-
throw new Error('Vapid public key is not a buffer.');
63+
if (typeof publicKey !== 'string') {
64+
throw new Error('Vapid public key is must be a URL safe Base 64 ' +
65+
'encoded string.');
8966
}
9067

68+
publicKey = urlBase64.decode(publicKey);
69+
9170
if (publicKey.length !== 65) {
92-
throw new Error('Vapid public key should be 65 bytes long');
71+
throw new Error('Vapid public key should be 65 bytes long when decoded.');
9372
}
73+
}
9474

75+
function validatePrivateKey(privateKey) {
9576
if (!privateKey) {
9677
throw new Error('No key set in vapid.privateKey');
9778
}
9879

99-
if (!(privateKey instanceof Buffer)) {
100-
throw new Error('Vapid private key is not a buffer');
80+
if (typeof privateKey !== 'string') {
81+
throw new Error('Vapid private key must be a URL safe Base 64 ' +
82+
'encoded string.');
10183
}
10284

85+
privateKey = urlBase64.decode(privateKey);
86+
10387
if (privateKey.length !== 32) {
104-
throw new Error('Vapid private key should be 32 bytes long');
88+
throw new Error('Vapid private key should be 32 bytes long when decoded.');
10589
}
90+
}
91+
92+
/**
93+
* This method takes the required VAPID parameters and returns the required
94+
* header to be added to a Web Push Protocol Request.
95+
* @param {string} audience This must be the origin of the push service.
96+
* @param {string} subject This should be a URL or a 'mailto:' email
97+
* address.
98+
* @param {Buffer} publicKey The VAPID public key.
99+
* @param {Buffer} privateKey The VAPID private key.
100+
* @param {integer} [expiration] The expiration of the VAPID JWT.
101+
* @return {Object} Returns an Object with the Authorization and
102+
* 'Crypto-Key' values to be used as headers.
103+
*/
104+
function getVapidHeaders(audience, subject, publicKey, privateKey, expiration) {
105+
if (!audience) {
106+
throw new Error('No audience set in vapid.audience.');
107+
}
108+
109+
if (typeof audience !== 'string' || audience.length === 0) {
110+
throw new Error('The audience value must be a string containing the ' +
111+
'origin of a push service. ' + audience);
112+
}
113+
114+
const audienceParseResult = url.parse(audience);
115+
if (!audienceParseResult.hostname) {
116+
throw new Error('VAPID audience is not a url. ' + audience);
117+
}
118+
119+
validateSubject(subject);
120+
validatePublicKey(publicKey);
121+
validatePrivateKey(privateKey);
122+
123+
publicKey = urlBase64.decode(publicKey);
124+
privateKey = urlBase64.decode(privateKey);
125+
106126

107127
if (expiration) {
108128
// TODO: Check if expiration is valid and use it in place of the hard coded
@@ -134,5 +154,8 @@ function getVapidHeaders(audience, subject, publicKey, privateKey, expiration) {
134154

135155
module.exports = {
136156
generateVAPIDKeys: generateVAPIDKeys,
137-
getVapidHeaders: getVapidHeaders
157+
getVapidHeaders: getVapidHeaders,
158+
validateSubject: validateSubject,
159+
validatePublicKey: validatePublicKey,
160+
validatePrivateKey: validatePrivateKey
138161
};

src/web-push-lib.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,10 @@ WebPushLib.prototype.setVapidDetails =
5555
return;
5656
}
5757

58+
vapidHelper.validateSubject(subject);
59+
vapidHelper.validatePublicKey(publicKey);
60+
vapidHelper.validatePrivateKey(privateKey);
61+
5862
vapidDetails = {
5963
subject: subject,
6064
publicKey: publicKey,

test/test-set-vapid-details.js

Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
'use strict';
2+
3+
const assert = require('assert');
4+
const urlBase64 = require('urlsafe-base64');
5+
const webPush = require('../src/index');
6+
7+
const VALID_SUBJECT_MAILTO = 'mailto: [email protected]';
8+
const VALID_SUBJECT_URL = 'https://exampe.com/contact';
9+
const VALID_PUBLIC_KEY = urlBase64.encode(new Buffer(65));
10+
const VALID_PRIVATE_KEY = urlBase64.encode(new Buffer(32));
11+
12+
suite('setVapidDetails()', function() {
13+
test('is defined', function() {
14+
assert(webPush.setVapidDetails);
15+
});
16+
17+
test('Valid URL input', function() {
18+
assert.doesNotThrow(function() {
19+
webPush.setVapidDetails(VALID_SUBJECT_URL, VALID_PUBLIC_KEY, VALID_PRIVATE_KEY);
20+
});
21+
});
22+
23+
test('Valid mailto: input', function() {
24+
assert.doesNotThrow(function() {
25+
webPush.setVapidDetails(VALID_SUBJECT_MAILTO, VALID_PUBLIC_KEY, VALID_PRIVATE_KEY);
26+
});
27+
});
28+
29+
test('reset Vapid Details with null', function() {
30+
assert.doesNotThrow(function() {
31+
webPush.setVapidDetails(null);
32+
});
33+
});
34+
35+
const invalidInputs = [
36+
{
37+
subject: '',
38+
publicKey: VALID_PUBLIC_KEY,
39+
privateKey: VALID_PRIVATE_KEY
40+
},
41+
{
42+
subject: 'This is not a valid subject',
43+
publicKey: VALID_PUBLIC_KEY,
44+
privateKey: VALID_PRIVATE_KEY
45+
},
46+
{
47+
subject: {},
48+
publicKey: VALID_PUBLIC_KEY,
49+
privateKey: VALID_PRIVATE_KEY
50+
},
51+
{
52+
subject: true,
53+
publicKey: VALID_PUBLIC_KEY,
54+
privateKey: VALID_PRIVATE_KEY
55+
},
56+
{
57+
subject: VALID_SUBJECT_URL,
58+
publicKey: urlBase64.encode(new Buffer(60)),
59+
privateKey: VALID_PRIVATE_KEY
60+
},
61+
{
62+
subject: VALID_SUBJECT_URL,
63+
publicKey: 'This is invalid',
64+
privateKey: VALID_PRIVATE_KEY
65+
},
66+
{
67+
subject: VALID_SUBJECT_URL,
68+
publicKey: '',
69+
privateKey: VALID_PRIVATE_KEY
70+
},
71+
{
72+
subject: VALID_SUBJECT_URL,
73+
publicKey: {},
74+
privateKey: VALID_PRIVATE_KEY
75+
},
76+
{
77+
subject: VALID_SUBJECT_URL,
78+
publicKey: true,
79+
privateKey: VALID_PRIVATE_KEY
80+
},
81+
{
82+
subject: VALID_SUBJECT_URL,
83+
publicKey: VALID_PUBLIC_KEY,
84+
privateKey: urlBase64.encode(new Buffer(60))
85+
},
86+
{
87+
subject: VALID_SUBJECT_URL,
88+
publicKey: VALID_PUBLIC_KEY,
89+
privateKey: 'This is invalid'
90+
},
91+
{
92+
subject: VALID_SUBJECT_URL,
93+
publicKey: VALID_PUBLIC_KEY,
94+
privateKey: ''
95+
},
96+
{
97+
subject: VALID_SUBJECT_URL,
98+
publicKey: VALID_PUBLIC_KEY,
99+
privateKey: {}
100+
},
101+
{
102+
subject: VALID_SUBJECT_URL,
103+
publicKey: VALID_PUBLIC_KEY,
104+
privateKey: true
105+
}
106+
];
107+
108+
test('Invalid input should throw an error', function() {
109+
invalidInputs.forEach(function(invalidInput, index) {
110+
assert.throws(function() {
111+
webPush.setVapidDetails(
112+
invalidInput.subject,
113+
invalidInput.publicKey,
114+
invalidInput.privateKey
115+
);
116+
}, 'Error not thrown on input index: ' + index);
117+
});
118+
});
119+
});

test/test-vapid-helper.js

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,15 @@
11
'use strict';
22

33
const assert = require('assert');
4+
const urlBase64 = require('urlsafe-base64');
45
const webPush = require('../src/index');
56
const vapidHelper = require('../src/vapid-helper');
67

78
const VALID_AUDIENCE = 'https://example.com';
89
const VALID_SUBJECT_MAILTO = 'mailto: [email protected]';
910
const VALID_SUBJECT_URL = 'https://exampe.com/contact';
10-
const VALID_PUBLIC_KEY = new Buffer(65);
11-
const VALID_PRIVATE_KEY = new Buffer(32);
11+
const VALID_PUBLIC_KEY = urlBase64.encode(new Buffer(65));
12+
const VALID_PRIVATE_KEY = urlBase64.encode(new Buffer(32));
1213
const VALID_EXPIRATION = Math.floor(Date.now() / 1000) + (60 * 60 * 12);
1314

1415
suite('Test Vapid Helpers', function() {
@@ -21,11 +22,11 @@ suite('Test Vapid Helpers', function() {
2122
assert(keys.privateKey);
2223
assert(keys.publicKey);
2324

24-
assert.equal(keys.privateKey instanceof Buffer, true);
25-
assert.equal(keys.publicKey instanceof Buffer, true);
25+
assert.equal(typeof keys.privateKey, 'string');
26+
assert.equal(typeof keys.publicKey, 'string');
2627

27-
assert.equal(keys.privateKey.length, 32);
28-
assert.equal(keys.publicKey.length, 65);
28+
assert.equal(urlBase64.decode(keys.privateKey).length, 32);
29+
assert.equal(urlBase64.decode(keys.publicKey).length, 65);
2930
});
3031

3132
test('generate new vapid keys between calls', function() {

test/testSelenium.js

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@
88
}
99

1010
/* eslint-disable global-require */
11-
const urlBase64 = require('urlsafe-base64');
1211
const seleniumAssistant = require('selenium-assistant');
1312
const webdriver = require('selenium-webdriver');
1413
const seleniumFirefox = require('selenium-webdriver/firefox');
@@ -26,11 +25,13 @@
2625
require('chromedriver');
2726
/* eslint-enable global-require */
2827

28+
const vapidKeys = webPush.generateVAPIDKeys();
29+
2930
const PUSH_TEST_TIMEOUT = 120 * 1000;
3031
const VAPID_PARAM = {
3132
subject: 'mailto:[email protected]',
32-
privateKey: new Buffer('H6tqEMswzHOFlPHFi2JPfDQRiKN32ZJIwvSPWZl1VTA=', 'base64'),
33-
publicKey: new Buffer('BIx6khu9Z/5lBwNEXYNEOQiL70IKYDpDxsTyoiCb82puQ/V4c/NFdyrBFpWdsz3mikmV6sWARNuhRbbbLTMOmB0=', 'base64')
33+
privateKey: vapidKeys.privateKey,
34+
publicKey: vapidKeys.publicKey
3435
};
3536
const testDirectory = './test/output/';
3637

@@ -119,7 +120,7 @@
119120
globalDriver = driver;
120121

121122
if (options.vapid) {
122-
testServerURL += '?vapid=' + urlBase64.encode(options.vapid.publicKey);
123+
testServerURL += '?vapid=' + options.vapid.publicKey;
123124
}
124125

125126
return globalDriver.get(testServerURL)

0 commit comments

Comments
 (0)