From ed01a23da10053c255575cee81fa9bc715bc1662 Mon Sep 17 00:00:00 2001 From: meltingice Date: Sun, 8 May 2011 23:07:57 -0400 Subject: [PATCH] Refactored OAuthEcho object into oauth.js to remove lots of redundancy --- Readme.md | 2 +- index.js | 2 +- lib/oauth.js | 86 +++++++++---- lib/oauthecho.js | 322 ----------------------------------------------- 4 files changed, 65 insertions(+), 347 deletions(-) delete mode 100644 lib/oauthecho.js diff --git a/Readme.md b/Readme.md index a54dbd7..71f821d 100644 --- a/Readme.md +++ b/Readme.md @@ -8,7 +8,7 @@ Also provides rudimentary OAuth2 support, tested against facebook connect and gi 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. +If you're running a node.js version in the 0.2x stable branch, 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. diff --git a/index.js b/index.js index 1649f74..e20716d 100644 --- a/index.js +++ b/index.js @@ -1,3 +1,3 @@ exports.OAuth = require("./lib/oauth").OAuth; -exports.OAuthEcho = require("./lib/oauthecho").OAuthEcho; +exports.OAuthEcho = require("./lib/oauth").OAuthEcho; exports.OAuth2 = require("./lib/oauth2").OAuth2; \ No newline at end of file diff --git a/lib/oauth.js b/lib/oauth.js index f05d5a8..11acf69 100644 --- a/lib/oauth.js +++ b/lib/oauth.js @@ -6,6 +6,8 @@ var crypto= require('crypto'), querystring= require('querystring'); exports.OAuth= function(requestUrl, accessUrl, consumerKey, consumerSecret, version, authorize_callback, signatureMethod, nonceSize, customHeaders) { + this._echo = false; + this._requestUrl= requestUrl; this._accessUrl= accessUrl; this._consumerKey= consumerKey; @@ -27,6 +29,26 @@ exports.OAuth= function(requestUrl, accessUrl, consumerKey, consumerSecret, vers "User-Agent" : "Node authentication"} }; +exports.OAuthEcho= function(realm, verify_credentials, consumerKey, consumerSecret, version, signatureMethod, nonceSize, customHeaders) { + this._echo = true; + + this._realm= realm; + this._verifyCredentials = verify_credentials; + this._consumerKey= consumerKey; + this._consumerSecret= this._encodeData( consumerSecret ); + this._version= version; + + if( signatureMethod != "PLAINTEXT" && signatureMethod != "HMAC-SHA1") + throw new Error("Un-supported signature method: " + signatureMethod ); + this._signatureMethod= signatureMethod; + this._nonceSize= nonceSize || 32; + this._headers= customHeaders || {"Accept" : "*/*", + "Connection" : "close", + "User-Agent" : "Node authentication"}; +} + +exports.OAuthEcho.prototype = exports.OAuth.prototype; + exports.OAuth.prototype._getTimestamp= function() { return Math.floor( (new Date()).getTime() / 1000 ); } @@ -85,13 +107,18 @@ exports.OAuth.prototype._isParameterNameAnOAuthParameter= function(parameter) { // build the OAuth request authorization header exports.OAuth.prototype._buildAuthorizationHeaders= function(orderedParameters) { var authHeader="OAuth "; + if (this._echo) { + authHeader += 'realm="' + this._realm + '",'; + } + for( var i= 0 ; i < orderedParameters.length; i++) { // Whilst the all the parameters should be included within the signature, only the oauth_ arguments // should appear within the authorization header. if( this._isParameterNameAnOAuthParameter(orderedParameters[i][0]) ) { - authHeader+= this._encodeData(orderedParameters[i][0])+"=\""+ this._encodeData(orderedParameters[i][1])+"\","; + authHeader+= "" + this._encodeData(orderedParameters[i][0])+"=\""+ this._encodeData(orderedParameters[i][1])+"\","; } } + authHeader= authHeader.substring(0, authHeader.length-1); return authHeader; } @@ -223,30 +250,37 @@ exports.OAuth.prototype._prepareParameters= function( oauth_token, oauth_token_s if( oauth_token ) { oauthParameters["oauth_token"]= oauth_token; } - if( extra_params ) { - for( var key in extra_params ) { - oauthParameters[key]= extra_params[key]; - } - } - var parsedUrl= URL.parse( url, false ); - - if( parsedUrl.query ) { - var key2; - var extraParameters= querystring.parse(parsedUrl.query); - for(var key in extraParameters ) { - var value= extraParameters[key]; - if( typeof value == "object" ){ - // TODO: This probably should be recursive - for(key2 in value){ - oauthParameters[key + "[" + key2 + "]"] = value[key2]; - } - } else { - oauthParameters[key]= value; - } + + var sig; + if (!this._echo) { + if( extra_params ) { + for( var key in extra_params ) { + oauthParameters[key]= extra_params[key]; } + } + var parsedUrl= URL.parse( url, false ); + + if( parsedUrl.query ) { + var key2; + var extraParameters= querystring.parse(parsedUrl.query); + for(var key in extraParameters ) { + var value= extraParameters[key]; + if( typeof value == "object" ){ + // TODO: This probably should be recursive + for(key2 in value){ + oauthParameters[key + "[" + key2 + "]"] = value[key2]; + } + } else { + oauthParameters[key]= value; + } + } + } + + sig = this._getSignature( method, url, this._normaliseRequestParams(oauthParameters), oauth_token_secret); + } else { + sig = this._getSignature( "GET", this._verifyCredentials, this._normaliseRequestParams(oauthParameters), oauth_token_secret); } - var sig= this._getSignature( method, url, this._normaliseRequestParams(oauthParameters), oauth_token_secret); var orderedParameters= this._sortRequestParams( this._makeArrayOfArgumentsHash(oauthParameters) ); orderedParameters[orderedParameters.length]= ["oauth_signature", sig]; return orderedParameters; @@ -263,7 +297,13 @@ exports.OAuth.prototype._performSecureRequest= function( oauth_token, oauth_toke if( parsedUrl.protocol == "https:" && !parsedUrl.port ) parsedUrl.port= 443; var headers= {}; - headers["Authorization"]= this._buildAuthorizationHeaders(orderedParameters); + var authorization = this._buildAuthorizationHeaders(orderedParameters); + if (this._echo) { + headers["X-Verify-Credentials-Authorization"]= authorization; + } else { + headers["Authorization"]= authorization; + } + headers["Host"] = parsedUrl.host for( var key in this._headers ) { diff --git a/lib/oauthecho.js b/lib/oauthecho.js deleted file mode 100644 index 141b8fc..0000000 --- a/lib/oauthecho.js +++ /dev/null @@ -1,322 +0,0 @@ -var crypto= require('crypto'), - sha1= require('./sha1'), - http= require('http'), - https= require('https'), - URL= require('url'), - querystring= require('querystring'); - -exports.OAuthEcho= function(realm, verify_credentials, consumerKey, consumerSecret, version, signatureMethod, nonceSize, customHeaders) { - this._realm= realm; - this._verifyCredentials = verify_credentials; - this._consumerKey= consumerKey; - this._consumerSecret= this._encodeData( consumerSecret ); - this._version= version; - - if( signatureMethod != "PLAINTEXT" && signatureMethod != "HMAC-SHA1") - throw new Error("Un-supported signature method: " + signatureMethod ); - this._signatureMethod= signatureMethod; - this._nonceSize= nonceSize || 32; - this._headers= customHeaders || {"Accept" : "*/*", - "Connection" : "close", - "User-Agent" : "Node authentication"}; -} - -exports.OAuthEcho.prototype.post = function(url, oauth_token, oauth_secret, post_body, post_content_type, callback) { - - var extra_params= null; - if( typeof post_content_type == "function" ) { - callback= post_content_type; - post_content_type= null; - } - if( typeof post_body != "string" ) { - post_content_type= "application/x-www-form-urlencoded" - extra_params= post_body; - post_body= null; - } - return this._performSecureRequest( oauth_token, oauth_secret, "POST", url, extra_params, post_body, post_content_type, callback ); -} - -exports.OAuthEcho.prototype._encodeData= function(toEncode){ - if( toEncode == null || toEncode == "" ) return "" - else { - var result= encodeURIComponent(toEncode); - // Fix the mismatch between OAuth's RFC3986's and Javascript's beliefs in what is right and wrong ;) - return result.replace(/\!/g, "%21") - .replace(/\'/g, "%27") - .replace(/\(/g, "%28") - .replace(/\)/g, "%29") - .replace(/\*/g, "%2A"); - } -} - -exports.OAuthEcho.prototype._getSignature= function(method, url, parameters, tokenSecret) { - var signatureBase= this._createSignatureBase(method, url, parameters); - return this._createSignature( signatureBase, tokenSecret ); -} - -exports.OAuthEcho.prototype._normalizeUrl= function(url) { - var parsedUrl= URL.parse(url, true) - var port =""; - if( parsedUrl.port ) { - if( (parsedUrl.protocol == "http:" && parsedUrl.port != "80" ) || - (parsedUrl.protocol == "https:" && parsedUrl.port != "443") ) { - port= ":" + parsedUrl.port; - } - } - - if( !parsedUrl.pathname || parsedUrl.pathname == "" ) parsedUrl.pathname ="/"; - - return parsedUrl.protocol + "//" + parsedUrl.hostname + port + parsedUrl.pathname; -} - -exports.OAuthEcho.prototype._getTimestamp= function() { - return Math.floor( (new Date()).getTime() / 1000 ); -} - -// Is the parameter considered an OAuth parameter -exports.OAuthEcho.prototype._isParameterNameAnOAuthParameter= function(parameter) { - var m = parameter.match('^oauth_'); - if( m && ( m[0] === "oauth_" ) ) { - return true; - } - else { - return false; - } -}; - -exports.OAuthEcho.prototype._performSecureRequest= function( oauth_token, oauth_token_secret, method, url, extra_params, post_body, post_content_type, callback ) { - - // Here we use verify_credentials instead of url - var orderedParameters= this._prepareParameters(url, oauth_token, oauth_token_secret, method, extra_params); - - if( !post_content_type ) { - post_content_type= "application/x-www-form-urlencoded"; - } - var parsedUrl= URL.parse( url, false ); - if( parsedUrl.protocol == "http:" && !parsedUrl.port ) parsedUrl.port= 80; - if( parsedUrl.protocol == "https:" && !parsedUrl.port ) parsedUrl.port= 443; - - var headers= {}; - - // OAuth Echo header - headers["X-Verify-Credentials-Authorization"]= this._buildAuthorizationHeaders(orderedParameters); - headers["Host"] = parsedUrl.host - - for( var key in this._headers ) { - if (this._headers.hasOwnProperty(key)) { - headers[key]= this._headers[key]; - } - } - - // Filter out any passed extra_params that are really to do with OAuth - for(var key in extra_params) { - if( this._isParameterNameAnOAuthParameter( key ) ) { - delete extra_params[key]; - } - } - - if( (method == "POST" || method == "PUT") && ( post_body == null && extra_params != null) ) { - post_body= querystring.stringify(extra_params); - } - - headers["Content-length"]= post_body ? post_body.length : 0; //Probably going to fail if not posting ascii - headers["Content-Type"]= post_content_type; - - var path; - if( !parsedUrl.pathname || parsedUrl.pathname == "" ) parsedUrl.pathname ="/"; - if( parsedUrl.query ) path= parsedUrl.pathname + "?"+ parsedUrl.query ; - else path= parsedUrl.pathname; - - 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.on('response', function (response) { - response.setEncoding('utf8'); - response.on('data', function (chunk) { - data+=chunk; - }); - response.on('end', function () { - if( response.statusCode != 200 ) { - // Follow 302 redirects with Location HTTP header - if(response.statusCode == 302 && response.headers && response.headers.location) { - self._performSecureRequest( oauth_token, oauth_token_secret, method, response.headers.location, extra_params, post_body, post_content_type, callback); - } - else { - callback({ statusCode: response.statusCode, data: data }, data, response); - } - } else { - callback(null, data, response); - } - }); - }); - - request.on("error", callback); - - if( (method == "POST" || method =="PUT") && post_body != null && post_body != "" ) { - request.write(post_body); - } - request.end(); - } - else { - if( (method == "POST" || method =="PUT") && post_body != null && post_body != "" ) { - request.write(post_body); - } - return request; - } - - return; -} - -exports.OAuthEcho.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.OAuthEcho.prototype._prepareParameters= function( url, oauth_token, oauth_token_secret, method, extra_params ) { - var oauthParameters= { - "oauth_timestamp": this._getTimestamp(), - "oauth_nonce": this._getNonce(this._nonceSize), - "oauth_version": this._version, - "oauth_signature_method": this._signatureMethod, - "oauth_consumer_key": this._consumerKey, - "oauth_token": oauth_token - }; - - var sig= this._getSignature( "GET", this._verifyCredentials, this._normaliseRequestParams(oauthParameters), oauth_token_secret); - var orderedParameters= this._sortRequestParams( this._makeArrayOfArgumentsHash(oauthParameters) ); - orderedParameters[orderedParameters.length]= ["oauth_signature", sig]; - - return orderedParameters; -} - -// build the OAuth request authorization header -exports.OAuthEcho.prototype._buildAuthorizationHeaders= function(orderedParameters) { - var authHeader='OAuth realm="' + this._realm + '"'; - for( var i= 0 ; i < orderedParameters.length; i++) { - // Whilst the all the parameters should be included within the signature, only the oauth_ arguments - // should appear within the authorization header. - if( this._isParameterNameAnOAuthParameter(orderedParameters[i][0]) ) { - authHeader+= ", " + this._encodeData(orderedParameters[i][0])+"=\""+ this._encodeData(orderedParameters[i][1])+"\""; - } - } - authHeader= authHeader.substring(0, authHeader.length-1); - return authHeader; -} - -// Takes an object literal that represents the arguments, and returns an array -// of argument/value pairs. -exports.OAuthEcho.prototype._makeArrayOfArgumentsHash= function(argumentsHash) { - var argument_pairs= []; - for(var key in argumentsHash ) { - var value= argumentsHash[key]; - if( Array.isArray(value) ) { - for(var i=0;i