UNPKG

passport-netlify

Version:

[Passport](http://passportjs.org/) strategy for authenticating with [Netlify](https://www.netlify.com/) using the OAuth 2.0 API.

1,614 lines (1,406 loc) 60.8 kB
import querystring from 'querystring'; import https from 'https'; import http from 'http'; import url from 'url'; import crypto from 'crypto'; import util from 'util'; /*! ***************************************************************************** Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 THIS CODE IS PROVIDED ON AN *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE, MERCHANTABLITY OR NON-INFRINGEMENT. See the Apache Version 2.0 License for specific language governing permissions and limitations under the License. ***************************************************************************** */ /* global Reflect, Promise */ var extendStatics = function(d, b) { extendStatics = Object.setPrototypeOf || ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; }; return extendStatics(d, b); }; function __extends(d, b) { extendStatics(d, b); function __() { this.constructor = d; } d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); } var __assign = function() { __assign = Object.assign || function __assign(t) { for (var s, i = 1, n = arguments.length; i < n; i++) { s = arguments[i]; for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) t[p] = s[p]; } return t; }; return __assign.apply(this, arguments); }; function createCommonjsModule(fn, module) { return module = { exports: {} }, fn(module, module.exports), module.exports; } /** * Creates an instance of `Strategy`. * * @constructor * @api public */ function Strategy() { } /** * Authenticate request. * * This function must be overridden by subclasses. In abstract form, it always * throws an exception. * * @param {Object} req The request to authenticate. * @param {Object} [options] Strategy-specific options. * @api public */ Strategy.prototype.authenticate = function(req, options) { throw new Error('Strategy#authenticate must be overridden by subclass'); }; /** * Expose `Strategy`. */ var strategy = Strategy; var lib = createCommonjsModule(function (module, exports) { /** * Module dependencies. */ /** * Expose `Strategy` directly from package. */ exports = module.exports = strategy; /** * Export constructors. */ exports.Strategy = strategy; }); var lib_1 = lib.Strategy; var utilsMerge = createCommonjsModule(function (module, exports) { /** * Merge object b with object a. * * var a = { foo: 'bar' } * , b = { bar: 'baz' }; * * merge(a, b); * // => { foo: 'bar', bar: 'baz' } * * @param {Object} a * @param {Object} b * @return {Object} * @api public */ exports = module.exports = function(a, b){ if (a && b) { for (var key in b) { a[key] = b[key]; } } return a; }; }); var merge = utilsMerge; /** * Reconstructs the original URL of the request. * * This function builds a URL that corresponds the original URL requested by the * client, including the protocol (http or https) and host. * * If the request passed through any proxies that terminate SSL, the * `X-Forwarded-Proto` header is used to detect if the request was encrypted to * the proxy, assuming that the proxy has been flagged as trusted. * * @param {http.IncomingMessage} req * @param {Object} [options] * @return {String} * @api private */ var originalURL = function(req, options) { options = options || {}; var app = req.app; if (app && app.get && app.get('trust proxy')) { options.proxy = true; } var trustProxy = options.proxy; var proto = (req.headers['x-forwarded-proto'] || '').toLowerCase() , tls = req.connection.encrypted || (trustProxy && 'https' == proto.split(/\s*,\s*/)[0]) , host = (trustProxy && req.headers['x-forwarded-host']) || req.headers.host , protocol = tls ? 'https' : 'http' , path = req.url || ''; return protocol + '://' + host + path; }; var utils = { merge: merge, originalURL: originalURL }; /* * A JavaScript implementation of the Secure Hash Algorithm, SHA-1, as defined * in FIPS 180-1 * Version 2.2 Copyright Paul Johnston 2000 - 2009. * Other contributors: Greg Holt, Andrew Kepert, Ydnar, Lostinet * Distributed under the BSD License * See http://pajhome.org.uk/crypt/md5 for details. */ var b64pad = "="; /* base-64 pad character. "=" for strict RFC compliance */ function b64_hmac_sha1(k, d) { return rstr2b64(rstr_hmac_sha1(str2rstr_utf8(k), str2rstr_utf8(d))); } /* * Calculate the HMAC-SHA1 of a key and some data (raw strings) */ function rstr_hmac_sha1(key, data) { var bkey = rstr2binb(key); if(bkey.length > 16) bkey = binb_sha1(bkey, key.length * 8); var ipad = Array(16), opad = Array(16); for(var i = 0; i < 16; i++) { ipad[i] = bkey[i] ^ 0x36363636; opad[i] = bkey[i] ^ 0x5C5C5C5C; } var hash = binb_sha1(ipad.concat(rstr2binb(data)), 512 + data.length * 8); return binb2rstr(binb_sha1(opad.concat(hash), 512 + 160)); } /* * Convert a raw string to a base-64 string */ function rstr2b64(input) { try { } catch(e) { b64pad=''; } var tab = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; var output = ""; var len = input.length; for(var i = 0; i < len; i += 3) { var triplet = (input.charCodeAt(i) << 16) | (i + 1 < len ? input.charCodeAt(i+1) << 8 : 0) | (i + 2 < len ? input.charCodeAt(i+2) : 0); for(var j = 0; j < 4; j++) { if(i * 8 + j * 6 > input.length * 8) output += b64pad; else output += tab.charAt((triplet >>> 6*(3-j)) & 0x3F); } } return output; } /* * Encode a string as utf-8. * For efficiency, this assumes the input is valid utf-16. */ function str2rstr_utf8(input) { var output = ""; var i = -1; var x, y; while(++i < input.length) { /* Decode utf-16 surrogate pairs */ x = input.charCodeAt(i); y = i + 1 < input.length ? input.charCodeAt(i + 1) : 0; if(0xD800 <= x && x <= 0xDBFF && 0xDC00 <= y && y <= 0xDFFF) { x = 0x10000 + ((x & 0x03FF) << 10) + (y & 0x03FF); i++; } /* Encode output as utf-8 */ if(x <= 0x7F) output += String.fromCharCode(x); else if(x <= 0x7FF) output += String.fromCharCode(0xC0 | ((x >>> 6 ) & 0x1F), 0x80 | ( x & 0x3F)); else if(x <= 0xFFFF) output += String.fromCharCode(0xE0 | ((x >>> 12) & 0x0F), 0x80 | ((x >>> 6 ) & 0x3F), 0x80 | ( x & 0x3F)); else if(x <= 0x1FFFFF) output += String.fromCharCode(0xF0 | ((x >>> 18) & 0x07), 0x80 | ((x >>> 12) & 0x3F), 0x80 | ((x >>> 6 ) & 0x3F), 0x80 | ( x & 0x3F)); } return output; } /* * Convert a raw string to an array of big-endian words * Characters >255 have their high-byte silently ignored. */ function rstr2binb(input) { var output = Array(input.length >> 2); for(var i = 0; i < output.length; i++) output[i] = 0; for(var i = 0; i < input.length * 8; i += 8) output[i>>5] |= (input.charCodeAt(i / 8) & 0xFF) << (24 - i % 32); return output; } /* * Convert an array of big-endian words to a string */ function binb2rstr(input) { var output = ""; for(var i = 0; i < input.length * 32; i += 8) output += String.fromCharCode((input[i>>5] >>> (24 - i % 32)) & 0xFF); return output; } /* * Calculate the SHA-1 of an array of big-endian words, and a bit length */ function binb_sha1(x, len) { /* append padding */ x[len >> 5] |= 0x80 << (24 - len % 32); x[((len + 64 >> 9) << 4) + 15] = len; var w = Array(80); var a = 1732584193; var b = -271733879; var c = -1732584194; var d = 271733878; var e = -1009589776; for(var i = 0; i < x.length; i += 16) { var olda = a; var oldb = b; var oldc = c; var oldd = d; var olde = e; for(var j = 0; j < 80; j++) { if(j < 16) w[j] = x[i + j]; else w[j] = bit_rol(w[j-3] ^ w[j-8] ^ w[j-14] ^ w[j-16], 1); var t = safe_add(safe_add(bit_rol(a, 5), sha1_ft(j, b, c, d)), safe_add(safe_add(e, w[j]), sha1_kt(j))); e = d; d = c; c = bit_rol(b, 30); b = a; a = t; } a = safe_add(a, olda); b = safe_add(b, oldb); c = safe_add(c, oldc); d = safe_add(d, oldd); e = safe_add(e, olde); } return Array(a, b, c, d, e); } /* * Perform the appropriate triplet combination function for the current * iteration */ function sha1_ft(t, b, c, d) { if(t < 20) return (b & c) | ((~b) & d); if(t < 40) return b ^ c ^ d; if(t < 60) return (b & c) | (b & d) | (c & d); return b ^ c ^ d; } /* * Determine the appropriate additive constant for the current iteration */ function sha1_kt(t) { return (t < 20) ? 1518500249 : (t < 40) ? 1859775393 : (t < 60) ? -1894007588 : -899497514; } /* * Add integers, wrapping at 2^32. This uses 16-bit operations internally * to work around bugs in some JS interpreters. */ function safe_add(x, y) { var lsw = (x & 0xFFFF) + (y & 0xFFFF); var msw = (x >> 16) + (y >> 16) + (lsw >> 16); return (msw << 16) | (lsw & 0xFFFF); } /* * Bitwise rotate a 32-bit number to the left. */ function bit_rol(num, cnt) { return (num << cnt) | (num >>> (32 - cnt)); } var HMACSHA1= function(key, data) { return b64_hmac_sha1(key, data); }; var sha1 = { HMACSHA1: HMACSHA1 }; // Returns true if this is a host that closes *before* it ends?!?! var isAnEarlyCloseHost= function( hostName ) { return hostName && hostName.match(".*google(apis)?.com$") }; var _utils = { isAnEarlyCloseHost: isAnEarlyCloseHost }; var oauth = createCommonjsModule(function (module, exports) { exports.OAuth= function(requestUrl, accessUrl, consumerKey, consumerSecret, version, authorize_callback, signatureMethod, nonceSize, customHeaders) { this._isEcho = false; this._requestUrl= requestUrl; this._accessUrl= accessUrl; this._consumerKey= consumerKey; this._consumerSecret= this._encodeData( consumerSecret ); if (signatureMethod == "RSA-SHA1") { this._privateKey = consumerSecret; } this._version= version; if( authorize_callback === undefined ) { this._authorize_callback= "oob"; } else { this._authorize_callback= authorize_callback; } if( signatureMethod != "PLAINTEXT" && signatureMethod != "HMAC-SHA1" && signatureMethod != "RSA-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"}; this._clientOptions= this._defaultClientOptions= {"requestTokenHttpMethod": "POST", "accessTokenHttpMethod": "POST", "followRedirects": true}; this._oauthParameterSeperator = ","; }; exports.OAuthEcho= function(realm, verify_credentials, consumerKey, consumerSecret, version, signatureMethod, nonceSize, customHeaders) { this._isEcho = true; this._realm= realm; this._verifyCredentials = verify_credentials; this._consumerKey= consumerKey; this._consumerSecret= this._encodeData( consumerSecret ); if (signatureMethod == "RSA-SHA1") { this._privateKey = consumerSecret; } this._version= version; if( signatureMethod != "PLAINTEXT" && signatureMethod != "HMAC-SHA1" && signatureMethod != "RSA-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"}; this._oauthParameterSeperator = ","; }; exports.OAuthEcho.prototype = exports.OAuth.prototype; 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 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.OAuth.prototype._decodeData= function(toDecode) { if( toDecode != null ) { toDecode = toDecode.replace(/\+/g, " "); } return decodeURIComponent( toDecode); }; exports.OAuth.prototype._getSignature= function(method, url$$1, parameters, tokenSecret) { var signatureBase= this._createSignatureBase(method, url$$1, parameters); return this._createSignature( signatureBase, tokenSecret ); }; exports.OAuth.prototype._normalizeUrl= function(url$$1) { var parsedUrl= url.parse(url$$1, 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; }; // Is the parameter considered an OAuth parameter exports.OAuth.prototype._isParameterNameAnOAuthParameter= function(parameter) { var m = parameter.match('^oauth_'); if( m && ( m[0] === "oauth_" ) ) { return true; } else { return false; } }; // build the OAuth request authorization header exports.OAuth.prototype._buildAuthorizationHeaders= function(orderedParameters) { var authHeader="OAuth "; if( this._isEcho ) { 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])+"\""+ this._oauthParameterSeperator; } } authHeader= authHeader.substring(0, authHeader.length-this._oauthParameterSeperator.length); return authHeader; }; // Takes an object literal that represents the arguments, and returns an array // of argument/value pairs. exports.OAuth.prototype._makeArrayOfArgumentsHash= function(argumentsHash) { var argument_pairs= []; for(var key in argumentsHash ) { if (argumentsHash.hasOwnProperty(key)) { var value= argumentsHash[key]; if( Array.isArray(value) ) { for(var i=0;i<value.length;i++) { argument_pairs[argument_pairs.length]= [key, value[i]]; } } else { argument_pairs[argument_pairs.length]= [key, value]; } } } return argument_pairs; }; // Sorts the encoded key value pairs by encoded name, then encoded value exports.OAuth.prototype._sortRequestParams= function(argument_pairs) { // 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(args) { var argument_pairs= this._makeArrayOfArgumentsHash(args); // First encode them #3.4.1.3.2 .1 for(var i=0;i<argument_pairs.length;i++) { argument_pairs[i][0]= this._encodeData( argument_pairs[i][0] ); argument_pairs[i][1]= this._encodeData( argument_pairs[i][1] ); } // Then sort them #3.4.1.3.2 .2 argument_pairs= this._sortRequestParams( argument_pairs ); // Then concatenate together #3.4.1.3.2 .3 & .4 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$$1, parameters) { url$$1= this._encodeData( this._normalizeUrl(url$$1) ); parameters= this._encodeData( parameters ); return method.toUpperCase() + "&" + url$$1 + "&" + parameters; }; exports.OAuth.prototype._createSignature= function(signatureBase, tokenSecret) { if( tokenSecret === undefined ) var tokenSecret= ""; else tokenSecret= this._encodeData( tokenSecret ); // consumerSecret is already encoded var key= this._consumerSecret + "&" + tokenSecret; var hash= ""; if( this._signatureMethod == "PLAINTEXT" ) { hash= key; } else if (this._signatureMethod == "RSA-SHA1") { key = this._privateKey || ""; hash= crypto.createSign("RSA-SHA1").update(signatureBase).sign(key, 'base64'); } else { if( crypto.Hmac ) { hash = crypto.createHmac("sha1", key).update(signatureBase).digest("base64"); } else { hash= sha1.HMACSHA1(key, signatureBase); } } return hash; }; 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._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$$1, 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 }; if( oauth_token ) { oauthParameters["oauth_token"]= oauth_token; } var sig; if( this._isEcho ) { sig = this._getSignature( "GET", this._verifyCredentials, this._normaliseRequestParams(oauthParameters), oauth_token_secret); } else { if( extra_params ) { for( var key in extra_params ) { if (extra_params.hasOwnProperty(key)) oauthParameters[key]= extra_params[key]; } } var parsedUrl= url.parse( url$$1, 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$$1, this._normaliseRequestParams(oauthParameters), oauth_token_secret); } var orderedParameters= this._sortRequestParams( this._makeArrayOfArgumentsHash(oauthParameters) ); orderedParameters[orderedParameters.length]= ["oauth_signature", sig]; return orderedParameters; }; exports.OAuth.prototype._performSecureRequest= function( oauth_token, oauth_token_secret, method, url$$1, extra_params, post_body, post_content_type, callback ) { var orderedParameters= this._prepareParameters(oauth_token, oauth_token_secret, method, url$$1, extra_params); if( !post_content_type ) { post_content_type= "application/x-www-form-urlencoded"; } var parsedUrl= url.parse( url$$1, false ); if( parsedUrl.protocol == "http:" && !parsedUrl.port ) parsedUrl.port= 80; if( parsedUrl.protocol == "https:" && !parsedUrl.port ) parsedUrl.port= 443; var headers= {}; var authorization = this._buildAuthorizationHeaders(orderedParameters); if ( this._isEcho ) { headers["X-Verify-Credentials-Authorization"]= authorization; } else { headers["Authorization"]= authorization; } 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) ) { // Fix the mismatch between the output of querystring.stringify() and this._encodeData() post_body= querystring.stringify(extra_params) .replace(/\!/g, "%21") .replace(/\'/g, "%27") .replace(/\(/g, "%28") .replace(/\)/g, "%29") .replace(/\*/g, "%2A"); } if( post_body ) { if ( Buffer.isBuffer(post_body) ) { headers["Content-length"]= post_body.length; } else { headers["Content-length"]= Buffer.byteLength(post_body); } } else { headers["Content-length"]= 0; } 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); } var clientOptions = this._clientOptions; if( callback ) { var data=""; var self= this; // Some hosts *cough* google appear to close the connection early / send no content-length header // allow this behaviour. var allowEarlyClose= _utils.isAnEarlyCloseHost( parsedUrl.hostname ); var callbackCalled= false; var passBackControl = function( response ) { if(!callbackCalled) { callbackCalled= true; if ( response.statusCode >= 200 && response.statusCode <= 299 ) { callback(null, data, response); } else { // Follow 301 or 302 redirects with Location HTTP header if((response.statusCode == 301 || response.statusCode == 302) && clientOptions.followRedirects && 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); } } } }; request.on('response', function (response) { response.setEncoding('utf8'); response.on('data', function (chunk) { data+=chunk; }); response.on('end', function () { passBackControl( response ); }); response.on('close', function () { if( allowEarlyClose ) { passBackControl( response ); } }); }); request.on("error", function(err) { if(!callbackCalled) { callbackCalled= true; callback( err ); } }); 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.OAuth.prototype.setClientOptions= function(options) { var key, mergedOptions= {}, hasOwnProperty= Object.prototype.hasOwnProperty; for( key in this._defaultClientOptions ) { if( !hasOwnProperty.call(options, key) ) { mergedOptions[key]= this._defaultClientOptions[key]; } else { mergedOptions[key]= options[key]; } } this._clientOptions= mergedOptions; }; exports.OAuth.prototype.getOAuthAccessToken= function(oauth_token, oauth_token_secret, oauth_verifier, callback) { var extraParams= {}; if( typeof oauth_verifier == "function" ) { callback= oauth_verifier; } else { extraParams.oauth_verifier= oauth_verifier; } this._performSecureRequest( oauth_token, oauth_token_secret, this._clientOptions.accessTokenHttpMethod, this._accessUrl, extraParams, null, null, function(error, data, response) { if( error ) callback(error); else { var results= querystring.parse( data ); var oauth_access_token= results["oauth_token"]; delete results["oauth_token"]; var oauth_access_token_secret= results["oauth_token_secret"]; delete results["oauth_token_secret"]; callback(null, oauth_access_token, oauth_access_token_secret, results ); } }); }; // Deprecated exports.OAuth.prototype.getProtectedResource= function(url$$1, method, oauth_token, oauth_token_secret, callback) { this._performSecureRequest( oauth_token, oauth_token_secret, method, url$$1, null, "", null, callback ); }; exports.OAuth.prototype.delete= function(url$$1, oauth_token, oauth_token_secret, callback) { return this._performSecureRequest( oauth_token, oauth_token_secret, "DELETE", url$$1, null, "", null, callback ); }; exports.OAuth.prototype.get= function(url$$1, oauth_token, oauth_token_secret, callback) { return this._performSecureRequest( oauth_token, oauth_token_secret, "GET", url$$1, null, "", null, callback ); }; exports.OAuth.prototype._putOrPost= function(method, url$$1, oauth_token, oauth_token_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" && !Buffer.isBuffer(post_body) ) { post_content_type= "application/x-www-form-urlencoded"; extra_params= post_body; post_body= null; } return this._performSecureRequest( oauth_token, oauth_token_secret, method, url$$1, extra_params, post_body, post_content_type, callback ); }; exports.OAuth.prototype.put= function(url$$1, oauth_token, oauth_token_secret, post_body, post_content_type, callback) { return this._putOrPost("PUT", url$$1, oauth_token, oauth_token_secret, post_body, post_content_type, callback); }; exports.OAuth.prototype.post= function(url$$1, oauth_token, oauth_token_secret, post_body, post_content_type, callback) { return this._putOrPost("POST", url$$1, oauth_token, oauth_token_secret, post_body, post_content_type, callback); }; /** * Gets a request token from the OAuth provider and passes that information back * to the calling code. * * The callback should expect a function of the following form: * * function(err, token, token_secret, parsedQueryString) {} * * This method has optional parameters so can be called in the following 2 ways: * * 1) Primary use case: Does a basic request with no extra parameters * getOAuthRequestToken( callbackFunction ) * * 2) As above but allows for provision of extra parameters to be sent as part of the query to the server. * getOAuthRequestToken( extraParams, callbackFunction ) * * N.B. This method will HTTP POST verbs by default, if you wish to override this behaviour you will * need to provide a requestTokenHttpMethod option when creating the client. * **/ exports.OAuth.prototype.getOAuthRequestToken= function( extraParams, callback ) { if( typeof extraParams == "function" ){ callback = extraParams; extraParams = {}; } // Callbacks are 1.0A related if( this._authorize_callback ) { extraParams["oauth_callback"]= this._authorize_callback; } this._performSecureRequest( null, null, this._clientOptions.requestTokenHttpMethod, this._requestUrl, extraParams, null, null, function(error, data, response) { if( error ) callback(error); else { var results= querystring.parse(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, results ); } }); }; exports.OAuth.prototype.signUrl= function(url$$1, oauth_token, oauth_token_secret, method) { if( method === undefined ) { var method= "GET"; } var orderedParameters= this._prepareParameters(oauth_token, oauth_token_secret, method, url$$1, {}); var parsedUrl= url.parse( url$$1, false ); var query=""; for( var i= 0 ; i < orderedParameters.length; i++) { query+= orderedParameters[i][0]+"="+ this._encodeData(orderedParameters[i][1]) + "&"; } query= query.substring(0, query.length-1); return parsedUrl.protocol + "//"+ parsedUrl.host + parsedUrl.pathname + "?" + query; }; exports.OAuth.prototype.authHeader= function(url$$1, oauth_token, oauth_token_secret, method) { if( method === undefined ) { var method= "GET"; } var orderedParameters= this._prepareParameters(oauth_token, oauth_token_secret, method, url$$1, {}); return this._buildAuthorizationHeaders(orderedParameters); }; }); var oauth_1 = oauth.OAuth; var oauth_2 = oauth.OAuthEcho; var oauth2 = createCommonjsModule(function (module, exports) { exports.OAuth2= function(clientId, clientSecret, baseSite, authorizePath, accessTokenPath, customHeaders) { this._clientId= clientId; this._clientSecret= clientSecret; this._baseSite= baseSite; this._authorizeUrl= authorizePath || "/oauth/authorize"; this._accessTokenUrl= accessTokenPath || "/oauth/access_token"; this._accessTokenName= "access_token"; this._authMethod= "Bearer"; this._customHeaders = customHeaders || {}; this._useAuthorizationHeaderForGET= false; //our agent this._agent = undefined; }; // Allows you to set an agent to use instead of the default HTTP or // HTTPS agents. Useful when dealing with your own certificates. exports.OAuth2.prototype.setAgent = function(agent) { this._agent = agent; }; // This 'hack' method is required for sites that don't use // 'access_token' as the name of the access token (for requests). // ( http://tools.ietf.org/html/draft-ietf-oauth-v2-16#section-7 ) // it isn't clear what the correct value should be atm, so allowing // for specific (temporary?) override for now. exports.OAuth2.prototype.setAccessTokenName= function ( name ) { this._accessTokenName= name; }; // Sets the authorization method for Authorization header. // e.g. Authorization: Bearer <token> # "Bearer" is the authorization method. exports.OAuth2.prototype.setAuthMethod = function ( authMethod ) { this._authMethod = authMethod; }; // If you use the OAuth2 exposed 'get' method (and don't construct your own _request call ) // this will specify whether to use an 'Authorize' header instead of passing the access_token as a query parameter exports.OAuth2.prototype.useAuthorizationHeaderforGET = function(useIt) { this._useAuthorizationHeaderForGET= useIt; }; exports.OAuth2.prototype._getAccessTokenUrl= function() { return this._baseSite + this._accessTokenUrl; /* + "?" + querystring.stringify(params); */ }; // Build the authorization header. In particular, build the part after the colon. // e.g. Authorization: Bearer <token> # Build "Bearer <token>" exports.OAuth2.prototype.buildAuthHeader= function(token) { return this._authMethod + ' ' + token; }; exports.OAuth2.prototype._chooseHttpLibrary= function( parsedUrl ) { var http_library= https; // As this is OAUth2, we *assume* https unless told explicitly otherwise. if( parsedUrl.protocol != "https:" ) { http_library= http; } return http_library; }; exports.OAuth2.prototype._request= function(method, url$$1, headers, post_body, access_token, callback) { var parsedUrl= url.parse( url$$1, true ); if( parsedUrl.protocol == "https:" && !parsedUrl.port ) { parsedUrl.port= 443; } var http_library= this._chooseHttpLibrary( parsedUrl ); var realHeaders= {}; for( var key in this._customHeaders ) { realHeaders[key]= this._customHeaders[key]; } if( headers ) { for(var key in headers) { realHeaders[key] = headers[key]; } } realHeaders['Host']= parsedUrl.host; if (!realHeaders['User-Agent']) { realHeaders['User-Agent'] = 'Node-oauth'; } if( post_body ) { if ( Buffer.isBuffer(post_body) ) { realHeaders["Content-Length"]= post_body.length; } else { realHeaders["Content-Length"]= Buffer.byteLength(post_body); } } else { realHeaders["Content-length"]= 0; } if( access_token && !('Authorization' in realHeaders)) { if( ! parsedUrl.query ) parsedUrl.query= {}; parsedUrl.query[this._accessTokenName]= access_token; } var queryStr= querystring.stringify(parsedUrl.query); if( queryStr ) queryStr= "?" + queryStr; var options = { host:parsedUrl.hostname, port: parsedUrl.port, path: parsedUrl.pathname + queryStr, method: method, headers: realHeaders }; this._executeRequest( http_library, options, post_body, callback ); }; exports.OAuth2.prototype._executeRequest= function( http_library, options, post_body, callback ) { // Some hosts *cough* google appear to close the connection early / send no content-length header // allow this behaviour. var allowEarlyClose= _utils.isAnEarlyCloseHost(options.host); var callbackCalled= false; function passBackControl( response, result ) { if(!callbackCalled) { callbackCalled=true; if( !(response.statusCode >= 200 && response.statusCode <= 299) && (response.statusCode != 301) && (response.statusCode != 302) ) { callback({ statusCode: response.statusCode, data: result }); } else { callback(null, result, response); } } } var result= ""; //set the agent on the request options if (this._agent) { options.agent = this._agent; } var request = http_library.request(options); request.on('response', function (response) { response.on("data", function (chunk) { result+= chunk; }); response.on("close", function (err) { if( allowEarlyClose ) { passBackControl( response, result ); } }); response.addListener("end", function () { passBackControl( response, result ); }); }); request.on('error', function(e) { callbackCalled= true; callback(e); }); if( (options.method == 'POST' || options.method == 'PUT') && post_body ) { request.write(post_body); } request.end(); }; exports.OAuth2.prototype.getAuthorizeUrl= function( params ) { var params= params || {}; params['client_id'] = this._clientId; return this._baseSite + this._authorizeUrl + "?" + querystring.stringify(params); }; exports.OAuth2.prototype.getOAuthAccessToken= function(code, params, callback) { var params= params || {}; params['client_id'] = this._clientId; params['client_secret'] = this._clientSecret; var codeParam = (params.grant_type === 'refresh_token') ? 'refresh_token' : 'code'; params[codeParam]= code; var post_data= querystring.stringify( params ); var post_headers= { 'Content-Type': 'application/x-www-form-urlencoded' }; this._request("POST", this._getAccessTokenUrl(), post_headers, post_data, null, function(error, data, response) { if( error ) callback(error); else { var results; try { // As of http://tools.ietf.org/html/draft-ietf-oauth-v2-07 // responses should be in JSON results= JSON.parse( data ); } catch(e) { // .... However both Facebook + Github currently use rev05 of the spec // and neither seem to specify a content-type correctly in their response headers :( // clients of these services will suffer a *minor* performance cost of the exception // being thrown results= querystring.parse( data ); } var access_token= results["access_token"]; var refresh_token= results["refresh_token"]; delete results["refresh_token"]; callback(null, access_token, refresh_token, results); // callback results =-= } }); }; // Deprecated exports.OAuth2.prototype.getProtectedResource= function(url$$1, access_token, callback) { this._request("GET", url$$1, {}, "", access_token, callback ); }; exports.OAuth2.prototype.get= function(url$$1, access_token, callback) { if( this._useAuthorizationHeaderForGET ) { var headers= {'Authorization': this.buildAuthHeader(access_token) }; access_token= null; } else { headers= {}; } this._request("GET", url$$1, headers, "", access_token, callback ); }; }); var oauth2_1 = oauth2.OAuth2; var OAuth = oauth.OAuth; var OAuthEcho = oauth.OAuthEcho; var OAuth2 = oauth2.OAuth2; var oauth$1 = { OAuth: OAuth, OAuthEcho: OAuthEcho, OAuth2: OAuth2 }; function NullStore(options) { } NullStore.prototype.store = function(req, cb) { cb(); }; NullStore.prototype.verify = function(req, providedState, cb) { cb(null, true); }; var _null = NullStore; /** * Module dependencies */ /** * 62 characters in the ascii range that can be used in URLs without special * encoding. */ var UIDCHARS = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; /** * Make a Buffer into a string ready for use in URLs * * @param {String} * @returns {String} * @api private */ function tostr(bytes) { var r, i; r = []; for (i = 0; i < bytes.length; i++) { r.push(UIDCHARS[bytes[i] % UIDCHARS.length]); } return r.join(''); } /** * Generate an Unique Id * * @param {Number} length The number of chars of the uid * @param {Number} cb (optional) Callback for async uid generation * @api public */ function uid(length, cb) { if (typeof cb === 'undefined') { return tostr(crypto.pseudoRandomBytes(length)); } else { crypto.pseudoRandomBytes(length, function(err, bytes) { if (err) return cb(err); cb(null, tostr(bytes)); }); } } /** * Exports */ var uid2 = uid; /** * Creates an instance of `SessionStore`. * * This is the state store implementation for the OAuth2Strategy used when * the `state` option is enabled. It generates a random state and stores it in * `req.session` and verifies it when the service provider redirects the user * back to the application. * * This state store requires session support. If no session exists, an error * will be thrown. * * Options: * * - `key` The key in the session under which to store the state * * @constructor * @param {Object} options * @api public */ function SessionStore(options) { if (!options.key) { throw new TypeError('Session-based state store requires a session key'); } this._key = options.key; } /** * Store request state. * * This implementation simply generates a random string and stores the value in * the session, where it will be used for verification when the user is * redirected back to the application. * * @param {Object} req * @param {Function} callback * @api protected */ SessionStore.prototype.store = function(req, callback) { if (!req.session) { return callback(new Error('OAuth 2.0 authentication requires session support when using state. Did you forget to use express-session middleware?')); } var key = this._key; var state = uid2(24); if (!req.session[key]) { req.session[key] = {}; } req.session[key].state = state; callback(null, state); }; /** * Verify request state. * * This implementation simply compares the state parameter in the request to the * value generated earlier and stored in the session. * * @param {Object} req * @param {String} providedState * @param {Function} callback * @api protected */ SessionStore.prototype.verify = function(req, providedState, callback) { if (!req.session) { return callback(new Error('OAuth 2.0 authentication requires session support when using state. Did you forget to use express-session middleware?')); } var key = this._key; if (!req.session[key]) { return callback(null, false, { message: 'Unable to verify authorization request state.' }); } var state = req.session[key].state; if (!state) { return callback(null, false, { message: 'Unable to verify authorization request state.' }); } delete req.session[key].state; if (Object.keys(req.session[key]).length === 0) { delete req.session[key]; } if (state !== providedState) { return callback(null, false, { message: 'Invalid authorization request state.' }); } return callback(null, true); }; // Expose constructor. var session = SessionStore; /** * `AuthorizationError` error. * * AuthorizationError represents an error in response to an authorization * request. For details, refer to RFC 6749, section 4.1.2.1. * * References: * - [The OAuth 2.0 Authorization Framework](http://tools.ietf.org/html/rfc6749) * * @constructor * @param {String} [message] * @param {String} [code] * @param {String} [uri] * @param {Number} [status] * @api public */ function AuthorizationError(message, code, uri, status) { if (!status) { switch (code) { case 'access_denied': status = 403; break; case 'server_error': status = 502; break; case 'temporarily_unavailable': status = 503; break; } } Error.call(this); Error.captureStackTrace(this, this.constructor); this.name = this.constructor.name; this.message = message; this.code = code || 'server_error'; this.uri = uri; this.status = status || 500; } /** * Inherit from `Error`. */ AuthorizationError.prototype.__proto__ = Error.prototype; /** * Expose `AuthorizationError`. */ var authorizationerror = AuthorizationError; /** * `TokenError` error. * * TokenError represents an error received from a token endpoint. For details, * refer to RFC 6749, section 5.2. * * References: * - [The OAuth 2.0 Authorization Framework](http://tools.ietf.org/html/rfc6749) * * @constructor * @param {String} [message] * @param {String} [code] * @param {String} [uri] * @param {Number} [status] * @api public */ function TokenError(message, code, uri, status) { Error.call(this); Error.captureStackTrace(this, this.constructor); this.name = this.constructor.name; this.message = message; this.code = code || 'invalid_request'; this.uri = uri; this.status = status || 500; } /** * Inherit from `Error`. */ TokenError.prototype.__proto__ = Error.prototype; /** * Expose `TokenError`. */ var tokenerror = TokenError; /** * `InternalOAuthError` error. * * InternalOAuthError wraps errors generated by node-oauth. By wrapping these * objects, error messages can be formatted in a manner that aids in debugging * OAuth issues. * * @constructor * @param {String} [message] * @param {Object|Error} [err] * @api public */ function InternalOAuthError(message, err) { Error.call(this); Error.captureStackTrace(this, this.constructor); this.name = this.constructor.name; this.message = message; this.oauthError = err; } /** * Inherit from `Error`. */ InternalOAuthError.prototype.__proto__ = Error.prototype; /** * Returns a string representing the error. * * @return {String} * @api public */ InternalOAuthError.prototype.toString = function() { var m = this.name; if (this.message) { m += ': ' + this.message; } if (this.oauthError) { if (this.oauthError instanceof Error) { m = this.oauthError.toString(); } else if (this.oauthError.statusCode && this.oauthError.data) { m += ' (status: ' + this.oauthError.statusCode + ' data: ' + this.oauthError.data + ')'; } } return m; }; /** * Expose `InternalOAuthError`. */ var internaloautherror = InternalOAuthError; // Load modules. var OAuth2$1 = oauth$1.OAuth2; /** * Creates an instance of `OAuth2Strategy`. * * The OAuth 2.0 authentication strategy authenticates requests using the OAuth * 2.0 framework. * * OAuth 2.0 provides a facility for delegated authentication, whereby users can * authenticate using a third-party service such as Facebook. Delegating in * this manner involves a sequence of events, including redirecting the user to * the third-party service for authorization. Once authorization has been * granted, the user is redirected back to the application and an authorization * code can be used to obtain credentials. * * Applications must supply a `verify` callback, for which the function * signature is: * * function(accessToken, refreshToken, profile, done) { ... } * * The verify callback is responsible for finding or creating the user, and * invoking `done` with the following arguments: * * done(err, user, info); * * `user` should be set to `false` to indicate an authentication failure. * Additional `info` can optionally be passed as a third argument, typically * used to display informational messages. If an exception occured, `err` * should be set. * * Options: * * - `authorizationURL` URL used to obtain an authorization grant * - `tokenURL` URL used to obtain an access token * - `clientID` identifies client to service provider * - `clientSecret` secret used to establish ownership of the client identifer * - `callbackURL` URL to which the service provider will redirect the user after obtaining authorization * - `passReqToCallback` when `true`, `req` is the first argument to the verify callback (default: `false`) * * Examples: * * passport.use(new OAuth2Strategy({ * authorizationURL: 'https://www.example.com/oauth2/authorize', * tokenURL: 'https://www.example.com/oauth2/token', * clientID: '123-456-789', * clientSecret: 'shhh-its-a-secret' * callbackURL: 'https://www.example.net/auth/example/callback' * }, * function(accessToken, refreshToken, profile, done) { * User.findOrCreate(..., function (err, user) { * done(err, user); * }); * } * )); * * @constructor * @param {Object} options * @param {Function} verify * @api public */ function OAuth2Strategy(options, verify) { if (typeof options == 'function') { verify = options; options = undefined; } options = options || {}; if (!verify) { throw new TypeError('OAuth2Strategy requires a verify callback'); } if (!options.authorizationURL) { throw new TypeError('OAuth2Strategy requires a authorizationURL option'); } if (!options.tokenURL) { throw new TypeError('OAuth2Strategy requires a tokenURL option'); } if (!options.clientID) { throw new TypeError('OAuth2Strategy requires a clientID option'); } lib.Strategy.call(this); this.name = 'oauth2'; this._verify = verify; // NOTE: The _oauth2 property is considered "protected". Subclasses are // allowed to use it when making protected resource requests to retrieve // the user profile. this._oauth2 = new OAuth2$1(options.clientID, options.clientSecret, '', options.authorizationURL, options.tokenURL, options.customHeaders); this._callbackURL = options.callbackURL; this._scope = options.scope; this._scopeSeparator = options.scopeSeparator || ' '; this._key = options.sessionKey || ('oauth2:' + url.parse(options.authorizationURL).hostname); if (options.store) { this._stateStore = options.store; } else { if (options.state) { this._stateStore = new session({ key: this._key }); } else { this._stateStore = new _null(); } } this._trustProxy = options.proxy; this._passReqToCallback = options.passReqToCallback; this._skipUserProfile = (options.skipUserProfile === undefined) ? false : options.skipUserProfile; } // Inherit from `passport.Strategy`. util.inherits(OAuth2Strategy, lib.Strategy); /** * Authenticate request by delegating to a service provider using OAuth 2.0. * * @param {Object} req * @api protected */ OAuth2Strategy.prototype.authenticate = function(req, options) { options = options || {}; var self = this; if (req.query && req.query.error) { if (req.query.error == 'access_denied') { return this.fail({ message: req.query.error_description }); } else { return this.error(new authorizationerror(req.query.error_description, req.query.error, req.query.error_uri)); } } var callbackURL = options.callbackURL || this._callbackURL; if (callbackURL) { var parsed = url.parse(callbackURL); if (!pars