var sha1= require('./sha1'), http= require('http'); exports.OAuth= function(requestUrl, accessUrl, authorizeUrl, consumerKey, consumerSecret, version, signatureMethod) { this._requestUrl= requestUrl; this._accessUrl= accessUrl; this._authorizeUrl= authorizeUrl; this._consumerKey= consumerKey; this._consumerSecret= this._encodeData( consumerSecret ); this._version= version; this._signatureMethod= signatureMethod; }; exports.OAuth.prototype._getTimestamp= function() { return Math.floor( (new Date()).getTime() / 1000 ); } exports.OAuth.prototype._encodeData= function(toEncode){ if( toEncode == null || toEncode == "" ) return "" else { var result= encodeURIComponent(toEncode); // Fix the mismatch between OAuth's RFC2396'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.OAuth.prototype._decodeData= function(toDecode) { if( toDecode != null ) { toDecode = toDecode.replace(/\+/g, " "); } return decodeURIComponent( toDecode); } exports.OAuth.prototype._getSignature= function(method, url, parameters, tokenSecret) { var signatureBase= this._createSignatureBase(method, url, parameters); return this._createSignature( signatureBase, tokenSecret ); } exports.OAuth.prototype._splitQueryString= function(stringToSplit) { var result= {}; var parameters= stringToSplit.split("&"); for(var key in parameters) { var parameterPair= parameters[key].split("="); result[parameterPair[0]]= parameterPair[1]; } return result; } // Takes a literal in, then returns a sorted array exports.OAuth.prototype._sortRequestParams= function(argumentsHash) { var argument_pairs= []; for(var key in argumentsHash ) { argument_pairs[argument_pairs.length]= [key, argumentsHash[key]]; } // Sort by name, then value. argument_pairs.sort(function(a,b) { if ( a[0]== b[0] ) { return a[1] < b[1] ? -1 : 1; } else return a[0] < b[0] ? -1 : 1; }); return argument_pairs; } exports.OAuth.prototype._normaliseRequestParams= function(arguments) { var argument_pairs= this._sortRequestParams( arguments ); var args= ""; for(var i=0;i<argument_pairs.length;i++) { args+= argument_pairs[i][0]; args+= "=" args+= argument_pairs[i][1]; if( i < argument_pairs.length-1 ) args+= "&"; } return args; } exports.OAuth.prototype._createSignatureBase= function(method, url, parameters) { url= this._encodeData(url); parameters= this._encodeData(parameters); return method.toUpperCase() + "&" + url + "&" + parameters; } exports.OAuth.prototype._createSignature= function(signatureBase, tokenSecret) { if( tokenSecret === undefined ) var tokenSecret= ""; else tokenSecret= this._encodeData( tokenSecret ); var key= this._consumerSecret + "&" + tokenSecret; //TODO: whilst we support different signature methods being passed // we currenting only do SHA1-HMAC var hash= sha1.HMACSHA1(key, signatureBase); signature = this._encodeData(hash); return signature; } exports.OAuth.prototype.NONCE_CHARS= ['a','b','c','d','e','f','g','h','i','j','k','l','m','n', 'o','p','q','r','s','t','u','v','w','x','y','z','A','B', 'C','D','E','F','G','H','I','J','K','L','M','N','O','P', 'Q','R','S','T','U','V','W','X','Y','Z','0','1','2','3', '4','5','6','7','8','9']; exports.OAuth.prototype._getNonce= function(nonceSize) { var result = []; var chars= this.NONCE_CHARS; var char_pos; var nonce_chars_length= chars.length; for (var i = 0; i < nonceSize; i++) { char_pos= Math.floor(Math.random() * nonce_chars_length); result[i]= chars[char_pos]; } return result.join(''); } exports.OAuth.prototype.getOauthAccessToken= function(oauth_token, oauth_token_secret, callback) { require('sys').puts('getOauthAccessToken') var oauthParameters= { "oauth_timestamp": this._getTimestamp(), "oauth_nonce": this._getNonce(32), "oauth_version": this._version, "oauth_signature_method": this._signatureMethod, "oauth_consumer_key": this._consumerKey, "oauth_token": oauth_token }; var method= "GET"; var sig= this._getSignature( method, this._accessUrl, this._normaliseRequestParams(oauthParameters), oauth_token_secret); var orderedParameters= this._sortRequestParams( oauthParameters ); orderedParameters[orderedParameters.length]= ["oauth_signature", sig]; var query=""; for( var i= 0 ; i < orderedParameters.length; i++) { query+= orderedParameters[i][0]+"="+ orderedParameters[i][1] + "&"; } query= query.substring(0, query.length-1); var oauthProvider= http.createClient(80, 'twitter.com'); var headers= {'Host': 'twitter.com'} var request = oauthProvider.request("GET", "/oauth/access_token"+"?"+query, headers); var data=""; var self= this; request.addListener('response', function (response) { response.setEncoding('utf8'); response.addListener('data', function (chunk) { data+=chunk; }); response.addListener('end', function () { if( response.statusCode != 200 ) { callback( response.statusCode +" : " + data ); } else { var results= self._splitQueryString(data); var oauth_token= results["oauth_token"]; results["oauth_token"]= undefined; var oauth_token_secret= results["oauth_token_secret"]; results["oauth_token_secret"]= undefined; callback(null, oauth_token, oauth_token_secret, results ); } }); }); request.end(); } exports.OAuth.prototype.getOAuthRequestToken= function(callback) { require('sys').puts('getOauthRequestToken') var oauthParameters= { "oauth_timestamp": this._getTimestamp(), "oauth_nonce": this._getNonce(32), "oauth_version": this._version, "oauth_signature_method": this._signatureMethod, "oauth_consumer_key": this._consumerKey }; var method= "POST"; require('sys').puts(this._requestUrl) var sig= this._getSignature( method, this._requestUrl, this._normaliseRequestParams(oauthParameters)); var orderedParameters= this._sortRequestParams( oauthParameters ); orderedParameters[orderedParameters.length]= ["oauth_signature", sig]; var headers= {}; // build request authorization header var authHeader="OAuth "; for( var i= 0 ; i < orderedParameters.length; i++) { authHeader+= orderedParameters[i][0]+"=\""+orderedParameters[i][1] +"\","; } authHeader= authHeader.substring(0, authHeader.length-1); headers["Authorization"]= authHeader; headers["Host"] = "twitter.com" headers["Accept"]= "*/*" headers["Connection"]= "close" headers["User-Agent"]= "Express authentication" headers["Content-length"]= 0 headers["Content-Type"]= "application/x-www-form-urlencoded" var oauthProvider= http.createClient(80, 'twitter.com'); var request = oauthProvider.request(method, "/oauth/request_token", headers); var data=""; var self= this; request.addListener('response', function (response) { response.setEncoding('utf8'); response.addListener('data', function (chunk) { data+=chunk; }); response.addListener('end', function () { if( response.statusCode != 200 ) { callback( response.statusCode +" : " + data ); } else { var results= self._splitQueryString(data); var oauth_token= results["oauth_token"]; var oauth_token_secret= results["oauth_token_secret"]; delete results["oauth_token"]; delete results["oauth_token_secret"]; callback(null, oauth_token, oauth_token_secret, (self._authorizeUrl + oauth_token), results ); } }); }); request.end(); }