var https = require('https'), crypto = require('crypto'); var Parse = require('parse/node').Parse; var OAuth = function(options) { if(!options) { throw new Parse.Error(Parse.Error.INTERNAL_SERVER_ERROR, 'No options passed to OAuth'); } this.consumer_key = options.consumer_key; this.consumer_secret = options.consumer_secret; this.auth_token = options.auth_token; this.auth_token_secret = options.auth_token_secret; this.host = options.host; this.oauth_params = options.oauth_params || {}; }; OAuth.prototype.send = function(method, path, params, body){ var request = this.buildRequest(method, path, params, body); // Encode the body properly, the current Parse Implementation don't do it properly return new Promise(function(resolve, reject) { var httpRequest = https.request(request, function(res) { var data = ''; res.on('data', function(chunk) { data += chunk; }); res.on('end', function() { data = JSON.parse(data); resolve(data); }); }).on('error', function() { reject('Failed to make an OAuth request'); }); if (request.body) { httpRequest.write(request.body); } httpRequest.end(); }); }; OAuth.prototype.buildRequest = function(method, path, params, body) { if (path.indexOf("/") != 0) { path = "/" + path; } if (params && Object.keys(params).length > 0) { path += "?" + OAuth.buildParameterString(params); } var request = { host: this.host, path: path, method: method.toUpperCase() }; var oauth_params = this.oauth_params || {}; oauth_params.oauth_consumer_key = this.consumer_key; if(this.auth_token){ oauth_params["oauth_token"] = this.auth_token; } request = OAuth.signRequest(request, oauth_params, this.consumer_secret, this.auth_token_secret); if (body && Object.keys(body).length > 0) { request.body = OAuth.buildParameterString(body); } return request; } OAuth.prototype.get = function(path, params) { return this.send("GET", path, params); } OAuth.prototype.post = function(path, params, body) { return this.send("POST", path, params, body); } /* Proper string %escape encoding */ OAuth.encode = function(str) { // discuss at: http://phpjs.org/functions/rawurlencode/ // original by: Brett Zamir (http://brett-zamir.me) // input by: travc // input by: Brett Zamir (http://brett-zamir.me) // input by: Michael Grier // input by: Ratheous // bugfixed by: Kevin van Zonneveld (http://kevin.vanzonneveld.net) // bugfixed by: Brett Zamir (http://brett-zamir.me) // bugfixed by: Joris // reimplemented by: Brett Zamir (http://brett-zamir.me) // reimplemented by: Brett Zamir (http://brett-zamir.me) // note: This reflects PHP 5.3/6.0+ behavior // note: Please be aware that this function expects to encode into UTF-8 encoded strings, as found on // note: pages served as UTF-8 // example 1: rawurlencode('Kevin van Zonneveld!'); // returns 1: 'Kevin%20van%20Zonneveld%21' // example 2: rawurlencode('http://kevin.vanzonneveld.net/'); // returns 2: 'http%3A%2F%2Fkevin.vanzonneveld.net%2F' // example 3: rawurlencode('http://www.google.nl/search?q=php.js&ie=utf-8&oe=utf-8&aq=t&rls=com.ubuntu:en-US:unofficial&client=firefox-a'); // returns 3: 'http%3A%2F%2Fwww.google.nl%2Fsearch%3Fq%3Dphp.js%26ie%3Dutf-8%26oe%3Dutf-8%26aq%3Dt%26rls%3Dcom.ubuntu%3Aen-US%3Aunofficial%26client%3Dfirefox-a' str = (str + '') .toString(); // Tilde should be allowed unescaped in future versions of PHP (as reflected below), but if you want to reflect current // PHP behavior, you would need to add ".replace(/~/g, '%7E');" to the following. return encodeURIComponent(str) .replace(/!/g, '%21') .replace(/'/g, '%27') .replace(/\(/g, '%28') .replace(/\)/g, '%29') .replace(/\*/g, '%2A'); } OAuth.signatureMethod = "HMAC-SHA1"; OAuth.version = "1.0"; /* Generate a nonce */ OAuth.nonce = function(){ var text = ""; var possible = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; for(var i = 0; i < 30; i++) text += possible.charAt(Math.floor(Math.random() * possible.length)); return text; } OAuth.buildParameterString = function(obj){ // Sort keys and encode values if (obj) { var keys = Object.keys(obj).sort(); // Map key=value, join them by & return keys.map(function(key){ return key + "=" + OAuth.encode(obj[key]); }).join("&"); } return ""; } /* Build the signature string from the object */ OAuth.buildSignatureString = function(method, url, parameters){ return [method.toUpperCase(), OAuth.encode(url), OAuth.encode(parameters)].join("&"); } /* Retuns encoded HMAC-SHA1 from key and text */ OAuth.signature = function(text, key){ crypto = require("crypto"); return OAuth.encode(crypto.createHmac('sha1', key).update(text).digest('base64')); } OAuth.signRequest = function(request, oauth_parameters, consumer_secret, auth_token_secret){ oauth_parameters = oauth_parameters || {}; // Set default values if (!oauth_parameters.oauth_nonce) { oauth_parameters.oauth_nonce = OAuth.nonce(); } if (!oauth_parameters.oauth_timestamp) { oauth_parameters.oauth_timestamp = Math.floor(new Date().getTime() / 1000); } if (!oauth_parameters.oauth_signature_method) { oauth_parameters.oauth_signature_method = OAuth.signatureMethod; } if (!oauth_parameters.oauth_version) { oauth_parameters.oauth_version = OAuth.version; } if(!auth_token_secret){ auth_token_secret = ""; } // Force GET method if unset if (!request.method) { request.method = "GET" } // Collect all the parameters in one signatureParameters object var signatureParams = {}; var parametersToMerge = [request.params, request.body, oauth_parameters]; for(var i in parametersToMerge) { var parameters = parametersToMerge[i]; for(var k in parameters) { signatureParams[k] = parameters[k]; } } // Create a string based on the parameters var parameterString = OAuth.buildParameterString(signatureParams); // Build the signature string var url = "https://" + request.host + "" + request.path; var signatureString = OAuth.buildSignatureString(request.method, url, parameterString); // Hash the signature string var signatureKey = [OAuth.encode(consumer_secret), OAuth.encode(auth_token_secret)].join("&"); var signature = OAuth.signature(signatureString, signatureKey); // Set the signature in the params oauth_parameters.oauth_signature = signature; if(!request.headers){ request.headers = {}; } // Set the authorization header var authHeader = Object.keys(oauth_parameters).sort().map(function(key){ var value = oauth_parameters[key]; return key + '="' + value + '"'; }).join(", ") request.headers.Authorization = 'OAuth ' + authHeader; // Set the content type header request.headers["Content-Type"] = "application/x-www-form-urlencoded"; return request; } module.exports = OAuth;