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
JavaScript
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