From dc724470c580314866ae869bb72611c62ff615dc Mon Sep 17 00:00:00 2001 From: Jeremy Ruppel Date: Thu, 27 Oct 2016 10:00:38 -0700 Subject: [PATCH] Make OAuth nonce time-independent --- services/oauth.js | 30 +++++++++++++----------------- test/plugins.oauth.js | 5 +++-- test/services.oauth.js | 31 ++++++++++++++++++++++++++----- 3 files changed, 42 insertions(+), 24 deletions(-) diff --git a/services/oauth.js b/services/oauth.js index bdc4a89..ea67f8a 100644 --- a/services/oauth.js +++ b/services/oauth.js @@ -1,19 +1,6 @@ var request = require('superagent'); var crypto = require('crypto'); -/** - * Returns a nonce for the timestamp `time`. OAuth 1.0 defines a - * nonce as a value unique to a given timestamp. - * @param {Number} time - * @returns {String} - * @see https://oauth.net/core/1.0a/#nonce - * @private - */ - -function nonce(time) { - return crypto.createHash('sha1').update(String(time)).digest('hex'); -} - /** * Returns the HMAC-SHA1 digest of `text` and `key` in baset64 encoding. * @param {String} text @@ -142,6 +129,17 @@ OAuth.prototype.timestamp = function () { return Math.floor(Date.now() / 1000); }; +/** + * Generates a pseudo-random string. OAuth 1.0 defines a + * nonce as a value unique within a given timestamp in seconds. + * @returns {String} + * @see https://oauth.net/core/1.0a/#nonce + */ + +OAuth.prototype.nonce = function () { + return crypto.pseudoRandomBytes(32).toString('base64'); +}; + /** * Creates an object with the standard OAuth 1.0 query params * for this instance. @@ -149,11 +147,9 @@ OAuth.prototype.timestamp = function () { */ OAuth.prototype.params = function () { - var timestamp = this.timestamp(); - return { - oauth_nonce: nonce(timestamp), - oauth_timestamp: timestamp, + oauth_nonce: this.nonce(), + oauth_timestamp: this.timestamp(), oauth_consumer_key: this.consumerKey, oauth_signature_method: 'HMAC-SHA1', oauth_version: '1.0' diff --git a/test/plugins.oauth.js b/test/plugins.oauth.js index e1d84aa..5251223 100644 --- a/test/plugins.oauth.js +++ b/test/plugins.oauth.js @@ -11,6 +11,7 @@ describe('plugins/oauth', function () { beforeEach(function () { sandbox = sinon.sandbox.create(); sandbox.stub(OAuth.prototype, 'timestamp').returns(499166400); + sandbox.stub(OAuth.prototype, 'nonce').returns('p2m2bnHdXVIsQH0FUv0oN9XrJU57ak7dSSpHU36mn4k='); }); afterEach(function () { @@ -21,13 +22,13 @@ describe('plugins/oauth', function () { var api = nock('https://api.flickr.com') .get('/services/rest') .query({ - oauth_nonce: '2114a105bc84fafbd4a05333b0b7f836c5dba8db', + oauth_nonce: 'p2m2bnHdXVIsQH0FUv0oN9XrJU57ak7dSSpHU36mn4k=', oauth_consumer_key: 'consumer key', oauth_token: 'oauth token', oauth_version: '1.0', oauth_timestamp: 499166400, oauth_signature_method: 'HMAC-SHA1', - oauth_signature: 'a8DFIqDyb0o1tnB2XeqM85RFz6o=', + oauth_signature: '5WSz6hwZ6F8jbeYv3eyErif1ySo=', method: 'flickr.test.echo', foo: 'bar', format: 'json', diff --git a/test/services.oauth.js b/test/services.oauth.js index 6923f5a..68c1da7 100644 --- a/test/services.oauth.js +++ b/test/services.oauth.js @@ -10,6 +10,7 @@ describe('services/oauth', function () { beforeEach(function () { subject = new OAuth('consumer key', 'consumer secret'); sinon.stub(subject, 'timestamp').returns(499166400); + sinon.stub(subject, 'nonce').returns('p2m2bnHdXVIsQH0FUv0oN9XrJU57ak7dSSpHU36mn4k='); }); describe('#request', function () { @@ -18,13 +19,13 @@ describe('services/oauth', function () { var api = nock('https://www.flickr.com') .get('/services/oauth/request_token') .query({ - oauth_nonce: '2114a105bc84fafbd4a05333b0b7f836c5dba8db', + oauth_nonce: 'p2m2bnHdXVIsQH0FUv0oN9XrJU57ak7dSSpHU36mn4k=', oauth_timestamp: 499166400, oauth_consumer_key: subject.consumerKey, oauth_signature_method: 'HMAC-SHA1', oauth_version: '1.0', oauth_callback: 'https://www.example.com/callback', - oauth_signature: 'n9Lnt7f7j9LrDJ0U6X30SSHSmW4=' + oauth_signature: 'JC6IWgvysQg30vh3Xk6TjARQWps=' }) .reply(200, 'oauth_callback_confirmed=true&oauth_token=foo&oauth_token_secret=bar'); @@ -48,12 +49,12 @@ describe('services/oauth', function () { .query({ oauth_token: 'token', oauth_verifier: 'verfier', - oauth_nonce: '2114a105bc84fafbd4a05333b0b7f836c5dba8db', + oauth_nonce: 'p2m2bnHdXVIsQH0FUv0oN9XrJU57ak7dSSpHU36mn4k=', oauth_timestamp: 499166400, oauth_consumer_key: subject.consumerKey, oauth_signature_method: 'HMAC-SHA1', oauth_version: '1.0', - oauth_signature: 'kdVh2jMIk5AGoN/63AGQ4kexpSg=' + oauth_signature: '7+3k1AWzUyxOoNO4rymh0Txz5FA=' }) .reply(200, 'fullname=Jamal%20Fanaian&oauth_token=foo&oauth_token_secret=bar&user_nsid=21207597%40N07&username=jamalfanaian'); @@ -82,12 +83,32 @@ describe('services/oauth', function () { }); + describe('#nonce', function () { + + beforeEach(function () { + sinon.restore(subject.nonce); + }); + + it('returns a string', function () { + assert.equal(typeof subject.nonce(), 'string'); + }); + + it('returns 32 bytes of data', function () { + assert.equal(Buffer.byteLength(subject.nonce(), 'base64'), 32); + }); + + it('does not return the same nonce twice', function () { + assert.notEqual(subject.nonce(), subject.nonce()); + }); + + }); + describe('#params', function () { it('returns OAuth 1.0 params', function () { var params = subject.params(); - assert.equal(params.oauth_nonce, '2114a105bc84fafbd4a05333b0b7f836c5dba8db'); + assert.equal(params.oauth_nonce, 'p2m2bnHdXVIsQH0FUv0oN9XrJU57ak7dSSpHU36mn4k='); assert.equal(params.oauth_timestamp, 499166400); assert.equal(params.oauth_consumer_key, subject.consumerKey); assert.equal(params.oauth_signature_method, 'HMAC-SHA1');