diff --git a/Readme.md b/Readme.md index d91bf127..d5445660 100644 --- a/Readme.md +++ b/Readme.md @@ -7,8 +7,15 @@ Tested against both Twitter (http://twitter.com), term.ie (http://term.ie/oauth Also provides rudimentary OAuth2 support, tested against facebook connect and github. For more complete usage examples please take a look at connect-auth (http://github.com/ciaranj/connect-auth) +If you're running a node.js version more recent than 0.4 then you will need to use a version of node.js greater than or equal to 0.9.0. +If you're running a node.js version in the 0.2x stable branc, then you will need to use version 0.8.4. + +Please be aware that when moving from 0.8.x to 0.9.0 there are no major API changes your, I've bumped the semi-major version element +so that I can release fixes to the 0.8.x stream if problems come out. + Change History ============== +* 0.9.0 - Compatibility fixes to bring node-oauth up to speed with node.js 0.4x [thanks to Rasmus Andersson for starting the work ] * 0.8.4 - Fixed issue #14 (Parameter ordering ignored encodings). Added support for repeated parameter names. Implements issue #15 (Use native SHA1 if available, 10x speed improvement!). Fixed issue #16 (Should use POST when requesting access tokens.). Fixed Issue #17 (OAuth2 spec compliance). Implemented enhancement #13 (Adds support for PUT & DELETE http verbs). Fixes issue #18 (Complex/Composite url arguments [thanks novemberborn]) * 0.8.3 - Fixed an issue where the auth header code depended on the Array's toString method (Yohei Sasaki) Updated the getOAuthRequestToken method so we can access google's OAuth secured methods. Also re-implemented and fleshed out the test suite. * 0.8.2 - The request returning methods will now write the POST body if provided (Chris Anderson), the code responsible for manipulating the headers is a bit safe now when working with other code (Paul McKellar) and tweaked the package.json to use index.js instead of main.js diff --git a/examples/term.ie.oauth-PLAINTEXT.js b/examples/term.ie.oauth-PLAINTEXT.js index bb8f804b..12c583a4 100644 --- a/examples/term.ie.oauth-PLAINTEXT.js +++ b/examples/term.ie.oauth-PLAINTEXT.js @@ -2,30 +2,33 @@ var sys= require('sys') var OAuth= require('../lib/oauth').OAuth; -var oa= new OAuth("http://term.ie/oauth/example/request_token.php?foo=bar", - "http://term.ie/oauth/example/access_token.php", - "key", - "secret", - "1.0", - null, - "PLAINTEXT") +var oa = new OAuth("http://term.ie/oauth/example/request_token.php?foo=bar", + "http://term.ie/oauth/example/access_token.php", + "key", + "secret", + "1.0", + null, + "PLAINTEXT") oa.getOAuthRequestToken(function(error, oauth_token, oauth_token_secret, results){ - if(error) sys.puts('error :' + error) - else { - sys.puts('oauth_token :' + oauth_token) - sys.puts('oauth_token_secret :' + oauth_token_secret) - sys.puts('requestoken results :' + sys.inspect(results)) - sys.puts("Requesting access token") - oa.getOAuthAccessToken(oauth_token, oauth_token_secret, function(error, oauth_access_token, oauth_access_token_secret, results2) { - sys.puts('oauth_access_token :' + oauth_access_token) - sys.puts('oauth_token_secret :' + oauth_access_token_secret) - sys.puts('accesstoken results :' + sys.inspect(results2)) - sys.puts("Requesting access token") - var data= ""; - oa.getProtectedResource("http://term.ie/oauth/example/echo_api.php?foo=bar&too=roo", "GET", oauth_access_token, oauth_access_token_secret, function (error, data, response) { - sys.puts(data); - }); + if (error) return console.log('error :' + error) + console.log('oauth_token :' + oauth_token) + console.log('oauth_token_secret :' + oauth_token_secret) + console.log('requestoken results :', results) + console.log("Requesting access token") + oa.getOAuthAccessToken(oauth_token, oauth_token_secret, + function(error, oauth_access_token, + oauth_access_token_secret, results2) { + console.log('oauth_access_token :' + oauth_access_token) + console.log('oauth_token_secret :' + oauth_access_token_secret) + console.log('accesstoken results :', results2) + console.log("Requesting access token") + var data= ""; + oa.getProtectedResource( + "http://term.ie/oauth/example/echo_api.php?foo=bar&too=roo", "GET", + oauth_access_token, oauth_access_token_secret, + function (error, data, response) { + console.log(data); }); - } + }); }) \ No newline at end of file diff --git a/lib/oauth.js b/lib/oauth.js index f6d64a1e..c662082f 100644 --- a/lib/oauth.js +++ b/lib/oauth.js @@ -1,6 +1,7 @@ var crypto= require('crypto'), sha1= require('./sha1'), http= require('http'), + https= require('https'), URL= require('url'), querystring= require('querystring'); @@ -193,8 +194,21 @@ exports.OAuth.prototype._getNonce= function(nonceSize) { return result.join(''); } -exports.OAuth.prototype._createClient= function( port, hostname, sslEnabled, credentials ) { - return http.createClient(port, hostname, sslEnabled, credentials); +exports.OAuth.prototype._createClient= function( port, hostname, method, path, headers, sslEnabled ) { + var options = { + host: hostname, + port: port, + path: path, + method: method, + headers: headers + }; + var httpModel; + if( sslEnabled ) { + httpModel= https; + } else { + httpModel= http; + } + return httpModel.request(options); } exports.OAuth.prototype._prepareParameters= function( oauth_token, oauth_token_secret, method, url, extra_params ) { @@ -248,14 +262,6 @@ exports.OAuth.prototype._performSecureRequest= function( oauth_token, oauth_toke if( parsedUrl.protocol == "http:" && !parsedUrl.port ) parsedUrl.port= 80; if( parsedUrl.protocol == "https:" && !parsedUrl.port ) parsedUrl.port= 443; - var oauthProvider; - if( parsedUrl.protocol == "https:" ) { - oauthProvider= this._createClient(parsedUrl.port, parsedUrl.hostname, true, crypto.createCredentials({})); - } - else { - oauthProvider= this._createClient(parsedUrl.port, parsedUrl.hostname); - } - var headers= {}; headers["Authorization"]= this._buildAuthorizationHeaders(orderedParameters); headers["Host"] = parsedUrl.host @@ -285,16 +291,23 @@ exports.OAuth.prototype._performSecureRequest= function( oauth_token, oauth_toke if( parsedUrl.query ) path= parsedUrl.pathname + "?"+ parsedUrl.query ; else path= parsedUrl.pathname; - var request = oauthProvider.request(method, path , headers); + var request; + if( parsedUrl.protocol == "https:" ) { + request= this._createClient(parsedUrl.port, parsedUrl.hostname, method, path, headers, true); + } + else { + request= this._createClient(parsedUrl.port, parsedUrl.hostname, method, path, headers); + } + if( callback ) { var data=""; var self= this; - request.addListener('response', function (response) { + request.on('response', function (response) { response.setEncoding('utf8'); - response.addListener('data', function (chunk) { + response.on('data', function (chunk) { data+=chunk; }); - response.addListener('end', function () { + response.on('end', function () { if( response.statusCode != 200 ) { callback({ statusCode: response.statusCode, data: data }); } else { @@ -303,7 +316,7 @@ exports.OAuth.prototype._performSecureRequest= function( oauth_token, oauth_toke }); }); - request.socket.addListener("error",callback); + request.on("error", callback); if( (method == "POST" || method =="PUT") && post_body != null && post_body != "" ) { request.write(post_body); diff --git a/lib/oauth2.js b/lib/oauth2.js index 8ab47775..6c86e10a 100644 --- a/lib/oauth2.js +++ b/lib/oauth2.js @@ -1,10 +1,8 @@ var querystring= require('querystring'), crypto= require('crypto'), - http= require('http'), + https= require('https'), URL= require('url'); -var sys= require('sys'); - exports.OAuth2= function(clientId, clientSecret, baseSite, authorizePath, accessTokenPath) { this._clientId= clientId; this._clientSecret= clientSecret; @@ -29,7 +27,6 @@ exports.OAuth2.prototype._request= function(method, url, headers, access_token, var creds = crypto.createCredentials({ }); var parsedUrl= URL.parse( url, true ); if( parsedUrl.protocol == "https:" && !parsedUrl.port ) parsedUrl.port= 443; - var httpClient = http.createClient(parsedUrl.port, parsedUrl.hostname, true, creds); var realHeaders= {}; if( headers ) { @@ -46,17 +43,17 @@ exports.OAuth2.prototype._request= function(method, url, headers, access_token, parsedUrl.query["access_token"]= access_token; } - var request = httpClient.request(method, parsedUrl.pathname + "?" + querystring.stringify(parsedUrl.query), realHeaders ); - - httpClient.addListener("secure", function () { -/* // disable verification for now. + var result= ""; -var verified = httpClient.verifyPeer(); - if(!verified) this.end(); */ - }); + var options = { + host:parsedUrl.hostname, + port: parsedUrl.port, + path: parsedUrl.pathname + "?" + querystring.stringify(parsedUrl.query), + method: method, + headers: realHeaders + }; - var result= ""; - request.addListener('response', function (response) { + request = https.request(options, function (response) { response.addListener("data", function (chunk) { result+= chunk }); @@ -69,6 +66,10 @@ var verified = httpClient.verifyPeer(); }); }); + request.on('error', function(e) { + callback(e); + }); + request.end(); } diff --git a/tests/oauth.js b/tests/oauth.js index 75908d54..0585a677 100644 --- a/tests/oauth.js +++ b/tests/oauth.js @@ -126,23 +126,21 @@ vows.describe('OAuth').addBatch({ 'When non standard ports are used': { topic: function() { var oa= new OAuth(null, null, null, null, null, null, "HMAC-SHA1"), - mockProvider= {}; - - mockProvider.request= function(method, path, headers) { + mockProvider= {}; + + oa._createClient= function( port, hostname, method, path, headers, sshEnabled ) { assert.equal(headers.Host, "somehost.com:8080"); - return result= {addListener:function(){}, - end:function(){}, - socket: {addListener: function(){}}}; - } - oa._createClient= function(port, host) { - assert.equal(port, '8080'); - assert.equal(host, 'somehost.com'); - return mockProvider; + assert.equal(hostname, "somehost.com"); + assert.equal(port, "8080"); + return { + on: function() {}, + end: function() {} + }; } return oa; }, - 'getProtectedResrouce should correctly define the host headers': function(oa) { - oa.getProtectedResource("http://somehost.com:8080", "GET", "oauth_token", null, function(){require('sys').p('dddd')}) + 'getProtectedResource should correctly define the host headers': function(oa) { + oa.getProtectedResource("http://somehost.com:8080", "GET", "oauth_token", null, function(){}) } }, 'When building the OAuth Authorization header': { @@ -194,20 +192,13 @@ vows.describe('OAuth').addBatch({ var post_body_written= false; var op= oa._createClient; try { - oa._createClient= function() { + oa._createClient= function( port, hostname, method, path, headers, sshEnabled ) { return { - request: function(method, path, headers) { - return { - write: function(post_body){ - post_body_written= true; - assert.equal(post_body,"scope=foobar%2C1%2C2"); - }, - socket: {addListener: function(){}}, - addListener: function() {}, - end: function() {} - } + write: function(post_body){ + post_body_written= true; + assert.equal(post_body,"scope=foobar%2C1%2C2"); } - } + }; } oa._performSecureRequest("token", "token_secret", 'POST', 'http://foo.com/protected_resource', {"scope": "foobar,1,2"}); assert.equal(post_body_written, true); @@ -237,19 +228,14 @@ vows.describe('OAuth').addBatch({ var callbackCalled= false; var op= oa._createClient; try { - oa._createClient= function() { + oa._createClient= function( port, hostname, method, path, headers, sshEnabled ) { return { - request: function(method, path, headers) { - return { - write: function(){}, - socket: {addListener: function(){}}, - addListener: function() {}, - end: function() { - callbackCalled= true; - } - } + write: function(){}, + on: function() {}, + end: function() { + callbackCalled= true; } - } + }; } var request= oa.post("http://foo.com/blah", "token", "token_secret", "BLAH", "text/plain", function(e,d){}) assert.equal(callbackCalled, true); @@ -265,21 +251,17 @@ vows.describe('OAuth').addBatch({ var op= oa._createClient; try { var callbackCalled= false; - oa._createClient= function() { + oa._createClient= function( port, hostname, method, path, headers, sshEnabled ) { + assert.equal(headers["Content-Type"], "application/x-www-form-urlencoded") return { - request: function(method, path, headers) { - assert.equal(headers["Content-Type"], "application/x-www-form-urlencoded") - return { - socket: {addListener: function(){}}, - write: function(data) { - callbackCalled= true; - assert.equal(data, "foo=1%2C2%2C3&bar=1%2B2"); - }, - addListener: function() {}, - end: function() {} - } + write: function(data){ + callbackCalled= true; + assert.equal(data, "foo=1%2C2%2C3&bar=1%2B2"); + }, + on: function() {}, + end: function() { } - } + }; } var request= oa.post("http://foo.com/blah", "token", "token_secret", {"foo":"1,2,3", "bar":"1+2"}) assert.equal(callbackCalled, true); @@ -295,22 +277,18 @@ vows.describe('OAuth').addBatch({ var op= oa._createClient; try { var callbackCalled= false; - oa._createClient= function() { - return { - request: function(method, path, headers) { - assert.equal(headers["Content-Type"], "application/x-www-form-urlencoded"); - assert.equal(headers["Content-length"], 23); - return { - socket: {addListener: function(){}}, - write: function(data) { - callbackCalled= true; - assert.equal(data, "foo=1%2C2%2C3&bar=1%2B2"); - }, - addListener: function() {}, - end: function() {} - } - } - } + oa._createClient= function( port, hostname, method, path, headers, sshEnabled ) { + assert.equal(headers["Content-Type"], "application/x-www-form-urlencoded"); + assert.equal(headers["Content-length"], 23); + return { + write: function(data){ + callbackCalled= true; + assert.equal(data, "foo=1%2C2%2C3&bar=1%2B2"); + }, + on: function() {}, + end: function() { + } + }; } var request= oa.post("http://foo.com/blah", "token", "token_secret", "foo=1%2C2%2C3&bar=1%2B2") assert.equal(callbackCalled, true); @@ -325,23 +303,19 @@ vows.describe('OAuth').addBatch({ var op= oa._createClient; try { var callbackCalled= false; - oa._createClient= function() { + oa._createClient= function( port, hostname, method, path, headers, sshEnabled ) { + assert.equal(headers["Content-Type"], "unicorn/encoded"); + assert.equal(headers["Content-length"], 23); return { - request: function(method, path, headers) { - assert.equal(headers["Content-Type"], "unicorn/encoded"); - assert.equal(headers["Content-length"], 23); - return { - socket: {addListener: function(){}}, - write: function(data) { - callbackCalled= true; - assert.equal(data, "foo=1%2C2%2C3&bar=1%2B2"); - }, - addListener: function() {}, - end: function() {} - } + write: function(data){ + callbackCalled= true; + assert.equal(data, "foo=1%2C2%2C3&bar=1%2B2"); + }, + on: function() {}, + end: function() { } - } - } + }; + } var request= oa.post("http://foo.com/blah", "token", "token_secret", "foo=1%2C2%2C3&bar=1%2B2", "unicorn/encoded") assert.equal(callbackCalled, true); } @@ -366,18 +340,13 @@ vows.describe('OAuth').addBatch({ var callbackCalled= false; var op= oa._createClient; try { - oa._createClient= function() { + oa._createClient= function( port, hostname, method, path, headers, sshEnabled ) { return { - request: function(method, path, headers) { - return { - socket: {addListener: function(){}}, - addListener: function() {}, - end: function() { - callbackCalled= true; - } - } + on: function() {}, + end: function() { + callbackCalled= true; } - } + }; } var request= oa.get("http://foo.com/blah", "token", "token_secret", function(e,d) {}) assert.equal(callbackCalled, true); @@ -400,25 +369,22 @@ vows.describe('OAuth').addBatch({ }, 'if a callback is passed' : { "it should call the internal request's end method and return nothing": function(oa) { - var callbackCalled= false; + var callbackCalled= 0; var op= oa._createClient; try { - oa._createClient= function() { + oa._createClient= function( port, hostname, method, path, headers, sshEnabled ) { return { - request: function(method, path, headers) { - return { - write: function(){}, - socket: {addListener: function(){}}, - addListener: function() {}, - end: function() { - callbackCalled= true; - } - } + on: function() {}, + write: function(data) { + callbackCalled++; + }, + end: function() { + callbackCalled++; } - } + }; } var request= oa.put("http://foo.com/blah", "token", "token_secret", "BLAH", "text/plain", function(e,d){}) - assert.equal(callbackCalled, true); + assert.equal(callbackCalled, 2); assert.isUndefined(request); } finally { @@ -431,21 +397,14 @@ vows.describe('OAuth').addBatch({ var op= oa._createClient; try { var callbackCalled= false; - oa._createClient= function() { + oa._createClient= function( port, hostname, method, path, headers, sshEnabled ) { + assert.equal(headers["Content-Type"], "application/x-www-form-urlencoded") return { - request: function(method, path, headers) { - assert.equal(headers["Content-Type"], "application/x-www-form-urlencoded") - return { - socket: {addListener: function(){}}, - write: function(data) { - callbackCalled= true; - assert.equal(data, "foo=1%2C2%2C3&bar=1%2B2"); - }, - addListener: function() {}, - end: function() {} - } + write: function(data) { + callbackCalled= true; + assert.equal(data, "foo=1%2C2%2C3&bar=1%2B2"); } - } + }; } var request= oa.put("http://foo.com/blah", "token", "token_secret", {"foo":"1,2,3", "bar":"1+2"}) assert.equal(callbackCalled, true); @@ -458,25 +417,18 @@ vows.describe('OAuth').addBatch({ 'if the post_body is a string' : { "and no post_content_type is specified" : { "It should be written as is, with a content length specified, and the encoding should be set to be x-www-form-urlencoded" : function(oa) { - var op= oa._createClient; - try { - var callbackCalled= false; - oa._createClient= function() { + var op= oa._createClient; + try { + var callbackCalled= false; + oa._createClient= function( port, hostname, method, path, headers, sshEnabled ) { + assert.equal(headers["Content-Type"], "application/x-www-form-urlencoded"); + assert.equal(headers["Content-length"], 23); return { - request: function(method, path, headers) { - assert.equal(headers["Content-Type"], "application/x-www-form-urlencoded"); - assert.equal(headers["Content-length"], 23); - return { - socket: {addListener: function(){}}, - write: function(data) { - callbackCalled= true; - assert.equal(data, "foo=1%2C2%2C3&bar=1%2B2"); - }, - addListener: function() {}, - end: function() {} - } + write: function(data) { + callbackCalled= true; + assert.equal(data, "foo=1%2C2%2C3&bar=1%2B2"); } - } + }; } var request= oa.put("http://foo.com/blah", "token", "token_secret", "foo=1%2C2%2C3&bar=1%2B2") assert.equal(callbackCalled, true); @@ -491,22 +443,15 @@ vows.describe('OAuth').addBatch({ var op= oa._createClient; try { var callbackCalled= false; - oa._createClient= function() { + oa._createClient= function( port, hostname, method, path, headers, sshEnabled ) { + assert.equal(headers["Content-Type"], "unicorn/encoded"); + assert.equal(headers["Content-length"], 23); return { - request: function(method, path, headers) { - assert.equal(headers["Content-Type"], "unicorn/encoded"); - assert.equal(headers["Content-length"], 23); - return { - socket: {addListener: function(){}}, - write: function(data) { - callbackCalled= true; - assert.equal(data, "foo=1%2C2%2C3&bar=1%2B2"); - }, - addListener: function() {}, - end: function() {} - } - } - } + write: function(data) { + callbackCalled= true; + assert.equal(data, "foo=1%2C2%2C3&bar=1%2B2"); + } + }; } var request= oa.put("http://foo.com/blah", "token", "token_secret", "foo=1%2C2%2C3&bar=1%2B2", "unicorn/encoded") assert.equal(callbackCalled, true); @@ -532,18 +477,13 @@ vows.describe('OAuth').addBatch({ var callbackCalled= false; var op= oa._createClient; try { - oa._createClient= function() { + oa._createClient= function( port, hostname, method, path, headers, sshEnabled ) { return { - request: function(method, path, headers) { - return { - socket: {addListener: function(){}}, - addListener: function() {}, - end: function() { - callbackCalled= true; - } - } + on: function() {}, + end: function() { + callbackCalled= true; } - } + }; } var request= oa.delete("http://foo.com/blah", "token", "token_secret", function(e,d) {}) assert.equal(callbackCalled, true);