2230 lines
61 KiB
JavaScript
2230 lines
61 KiB
JavaScript
|
/**
|
||
|
* Module dependencies.
|
||
|
*/
|
||
|
|
||
|
var Base64Url = require('./lib/base64_url');
|
||
|
var assert_required = require('./lib/assert_required');
|
||
|
var is_array = require('./lib/is-array');
|
||
|
var index_of = require('./lib/index-of');
|
||
|
var nonceGenerator = require('./lib/nonce-generator');
|
||
|
|
||
|
var qs = require('qs');
|
||
|
var xtend = require('xtend');
|
||
|
var trim = require('trim');
|
||
|
var reqwest = require('reqwest');
|
||
|
var WinChan = require('winchan');
|
||
|
|
||
|
var jsonp = require('jsonp');
|
||
|
var jsonpOpts = { param: 'cbx', timeout: 8000, prefix: '__auth0jp' };
|
||
|
|
||
|
var same_origin = require('./lib/same-origin');
|
||
|
var json_parse = require('./lib/json-parse');
|
||
|
var LoginError = require('./lib/LoginError');
|
||
|
var use_jsonp = require('./lib/use_jsonp');
|
||
|
|
||
|
var SilentAuthenticationHandler = require('./lib/SilentAuthenticationHandler');
|
||
|
|
||
|
/**
|
||
|
* Check if running in IE.
|
||
|
*
|
||
|
* @returns {Number} -1 if not IE, IE version otherwise.
|
||
|
*/
|
||
|
function isInternetExplorer() {
|
||
|
var rv = -1; // Return value assumes failure.
|
||
|
var ua = navigator.userAgent;
|
||
|
var re;
|
||
|
if (navigator.appName === 'Microsoft Internet Explorer') {
|
||
|
re = new RegExp('MSIE ([0-9]{1,}[\.0-9]{0,})');
|
||
|
if (re.exec(ua) != null) {
|
||
|
rv = parseFloat(RegExp.$1);
|
||
|
}
|
||
|
}
|
||
|
// IE > 11
|
||
|
else if (ua.indexOf('Trident') > -1) {
|
||
|
re = new RegExp('rv:([0-9]{2,2}[\.0-9]{0,})');
|
||
|
if (re.exec(ua) !== null) {
|
||
|
rv = parseFloat(RegExp.$1);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return rv;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Stringify popup options object into
|
||
|
* `window.open` string options format
|
||
|
*
|
||
|
* @param {Object} popupOptions
|
||
|
* @private
|
||
|
*/
|
||
|
|
||
|
function stringifyPopupSettings(popupOptions) {
|
||
|
var settings = '';
|
||
|
|
||
|
for (var key in popupOptions) {
|
||
|
settings += key + '=' + popupOptions[key] + ',';
|
||
|
}
|
||
|
|
||
|
return settings.slice(0, -1);
|
||
|
}
|
||
|
|
||
|
|
||
|
/**
|
||
|
* Check that a key has been set to something different than null
|
||
|
* or undefined.
|
||
|
*
|
||
|
* @param {Object} obj
|
||
|
* @param {String} key
|
||
|
*/
|
||
|
function checkIfSet(obj, key) {
|
||
|
/*
|
||
|
* false != null -> true
|
||
|
* true != null -> true
|
||
|
* undefined != null -> false
|
||
|
* null != null -> false
|
||
|
*/
|
||
|
return !!(obj && obj[key] != null);
|
||
|
}
|
||
|
|
||
|
function handleRequestError(err, callback) {
|
||
|
var status = err.status;
|
||
|
var responseText = 'string' === typeof err.responseText ? err.responseText : err;
|
||
|
|
||
|
var isAffectedIEVersion = isInternetExplorer() === 10 || isInternetExplorer() === 11;
|
||
|
var zeroStatus = (!status || status === 0);
|
||
|
|
||
|
var onLine = !!window.navigator.onLine;
|
||
|
|
||
|
// Request failed because we are offline.
|
||
|
if (zeroStatus && !onLine ) {
|
||
|
status = 0;
|
||
|
responseText = {
|
||
|
code: 'offline'
|
||
|
};
|
||
|
// http://stackoverflow.com/questions/23229723/ie-10-11-cors-status-0
|
||
|
// XXX IE10 when a request fails in CORS returns status code 0
|
||
|
// See: http://caniuse.com/#search=navigator.onLine
|
||
|
} else if (zeroStatus && isAffectedIEVersion) {
|
||
|
status = 401;
|
||
|
responseText = {
|
||
|
code: 'invalid_user_password'
|
||
|
};
|
||
|
// If not IE10/11 and not offline it means that Auth0 host is unreachable:
|
||
|
// Connection Timeout or Connection Refused.
|
||
|
} else if (zeroStatus) {
|
||
|
status = 0;
|
||
|
responseText = {
|
||
|
code: 'connection_refused_timeout'
|
||
|
};
|
||
|
}
|
||
|
|
||
|
var error = new LoginError(status, responseText);
|
||
|
callback(error);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* join url from protocol
|
||
|
*/
|
||
|
|
||
|
function joinUrl(protocol, domain, endpoint) {
|
||
|
return protocol + '//' + domain + endpoint;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Create an `Auth0` instance with `options`
|
||
|
*
|
||
|
* @class Auth0
|
||
|
* @constructor
|
||
|
*/
|
||
|
function Auth0 (options) {
|
||
|
// XXX Deprecated: We prefer new Auth0(...)
|
||
|
if (!(this instanceof Auth0)) {
|
||
|
return new Auth0(options);
|
||
|
}
|
||
|
|
||
|
assert_required(options, 'clientID');
|
||
|
assert_required(options, 'domain');
|
||
|
|
||
|
this._useJSONP = null != options.forceJSONP ?
|
||
|
!!options.forceJSONP :
|
||
|
use_jsonp() && !same_origin('https:', options.domain);
|
||
|
|
||
|
this._clientID = options.clientID;
|
||
|
this._callbackURL = options.callbackURL || document.location.href;
|
||
|
this._shouldRedirect = !!options.callbackURL;
|
||
|
this._domain = options.domain;
|
||
|
this._responseType = this._parseResponseType(options, true) || "code";
|
||
|
this._responseMode = this._parseResponseMode(options, true);
|
||
|
this._cordovaSocialPlugins = {
|
||
|
facebook: this._phonegapFacebookLogin
|
||
|
};
|
||
|
this._useCordovaSocialPlugins = false || options.useCordovaSocialPlugins;
|
||
|
this._sendClientInfo = null != options.sendSDKClientInfo ? options.sendSDKClientInfo : true;
|
||
|
|
||
|
this._scope = options.scope || 'openid';
|
||
|
this._audience = options.audience || null;
|
||
|
this._tenant = options.__tenant || this._domain.split('.')[0];
|
||
|
this._token_issuer = options.__token_issuer || 'https://' + this._domain + '/';
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Export version with `Auth0` constructor
|
||
|
*
|
||
|
* @property {String} version
|
||
|
*/
|
||
|
|
||
|
Auth0.version = require('./version').str;
|
||
|
|
||
|
/**
|
||
|
* Export client info object
|
||
|
*
|
||
|
*
|
||
|
* @property {Hash}
|
||
|
*/
|
||
|
|
||
|
Auth0.clientInfo = { name: 'auth0.js', version: Auth0.version };
|
||
|
|
||
|
|
||
|
/**
|
||
|
* Wraps calls to window.open so it can be overriden in Electron.
|
||
|
*
|
||
|
* In Electron, window.open returns an object which provides limited control
|
||
|
* over the opened window (see
|
||
|
* http://electron.atom.io/docs/v0.36.0/api/window-open/).
|
||
|
*/
|
||
|
Auth0.prototype.openWindow = function(url, name, options) {
|
||
|
return window.open(url, name, stringifyPopupSettings(options));
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Redirect current location to `url`
|
||
|
*
|
||
|
* @param {String} url
|
||
|
* @private
|
||
|
*/
|
||
|
|
||
|
Auth0.prototype._redirect = function (url) {
|
||
|
global.window.location = url;
|
||
|
};
|
||
|
|
||
|
Auth0.prototype._getResponseType = function(opts) {
|
||
|
return this._parseResponseType(opts) || this._responseType;
|
||
|
};
|
||
|
|
||
|
Auth0.prototype._getCallbackOnLocationHash = function(options) {
|
||
|
return this._getResponseMode(options) !== "form_post"
|
||
|
&& this._getResponseType(options) !== "code";
|
||
|
};
|
||
|
|
||
|
Auth0.prototype._getResponseMode = function(opts) {
|
||
|
var result = this._parseResponseMode(opts) || this._responseMode;
|
||
|
return result === "form_post"
|
||
|
? "form_post"
|
||
|
: null;
|
||
|
};
|
||
|
|
||
|
Auth0.prototype._getCallbackURL = function(options) {
|
||
|
return (options && typeof options.callbackURL !== 'undefined') ?
|
||
|
options.callbackURL : this._callbackURL;
|
||
|
};
|
||
|
|
||
|
Auth0.prototype._getClientInfoString = function () {
|
||
|
var clientInfo = JSON.stringify(Auth0.clientInfo);
|
||
|
return Base64Url.encode(clientInfo);
|
||
|
};
|
||
|
|
||
|
Auth0.prototype._getClientInfoHeader = function () {
|
||
|
return this._sendClientInfo
|
||
|
? { 'Auth0-Client': this._getClientInfoString() }
|
||
|
: {};
|
||
|
};
|
||
|
|
||
|
/**
|
||
|
* Renders and submits a WSFed form
|
||
|
*
|
||
|
* @param {Object} options
|
||
|
* @param {Function} formHtml
|
||
|
* @private
|
||
|
*/
|
||
|
|
||
|
Auth0.prototype._renderAndSubmitWSFedForm = function (options, formHtml) {
|
||
|
var div = document.createElement('div');
|
||
|
div.innerHTML = formHtml;
|
||
|
var form = document.body.appendChild(div).children[0];
|
||
|
|
||
|
if (options.popup && !this._getCallbackOnLocationHash(options)) {
|
||
|
form.target = 'auth0_signup_popup';
|
||
|
}
|
||
|
|
||
|
form.submit();
|
||
|
};
|
||
|
|
||
|
/**
|
||
|
* Resolve response type as `token` or `code`
|
||
|
*
|
||
|
* @return {Object} `scope` and `response_type` properties
|
||
|
* @private
|
||
|
*/
|
||
|
|
||
|
Auth0.prototype._getMode = function (options) {
|
||
|
var result = {
|
||
|
scope: this._scope,
|
||
|
response_type: this._getResponseType(options)
|
||
|
};
|
||
|
|
||
|
var responseMode = this._getResponseMode(options);
|
||
|
if (responseMode) {
|
||
|
result.response_mode = responseMode;
|
||
|
}
|
||
|
|
||
|
return result;
|
||
|
};
|
||
|
|
||
|
Auth0.prototype._configureOfflineMode = function(options) {
|
||
|
if (options.scope && options.scope.indexOf('offline_access') >= 0) {
|
||
|
options.device = options.device || 'Browser';
|
||
|
}
|
||
|
};
|
||
|
|
||
|
/**
|
||
|
* Get user information from API
|
||
|
*
|
||
|
* @param {Object} profile
|
||
|
* @param {String} id_token
|
||
|
* @param {Function} callback
|
||
|
* @private
|
||
|
*/
|
||
|
|
||
|
Auth0.prototype._getUserInfo = function (profile, id_token, callback) {
|
||
|
|
||
|
warn("DEPRECATION NOTICE: This method will be soon deprecated, use `getUserInfo` instead.")
|
||
|
|
||
|
if (!(profile && !profile.user_id)) {
|
||
|
return callback(null, profile);
|
||
|
}
|
||
|
|
||
|
// the scope was just openid
|
||
|
var _this = this;
|
||
|
var protocol = 'https:';
|
||
|
var domain = this._domain;
|
||
|
var endpoint = '/tokeninfo';
|
||
|
var url = joinUrl(protocol, domain, endpoint);
|
||
|
|
||
|
var fail = function (status, description) {
|
||
|
var error = new Error(status + ': ' + (description || ''));
|
||
|
|
||
|
// These two properties are added for compatibility with old versions (no Error instance was returned)
|
||
|
error.error = status;
|
||
|
error.error_description = description;
|
||
|
|
||
|
callback(error);
|
||
|
};
|
||
|
|
||
|
if (this._useJSONP) {
|
||
|
return jsonp(url + '?' + qs.stringify({id_token: id_token}), jsonpOpts, function (err, resp) {
|
||
|
if (err) {
|
||
|
return fail(0, err.toString());
|
||
|
}
|
||
|
|
||
|
return resp.status === 200 ?
|
||
|
callback(null, resp.user) :
|
||
|
fail(resp.status, resp.err || resp.error);
|
||
|
});
|
||
|
}
|
||
|
|
||
|
return reqwest({
|
||
|
url: same_origin(protocol, domain) ? endpoint : url,
|
||
|
method: 'post',
|
||
|
type: 'json',
|
||
|
crossOrigin: !same_origin(protocol, domain),
|
||
|
data: {id_token: id_token}
|
||
|
}).fail(function (err) {
|
||
|
fail(err.status, err.responseText);
|
||
|
}).then(function (userinfo) {
|
||
|
callback(null, userinfo);
|
||
|
});
|
||
|
|
||
|
};
|
||
|
|
||
|
/**
|
||
|
* Get user information from API
|
||
|
*
|
||
|
* @param {Object} profile
|
||
|
* @param {String} id_token
|
||
|
* @param {Function} callback
|
||
|
* @private
|
||
|
*/
|
||
|
|
||
|
Auth0.prototype.getUserInfo = function (access_token, callback) {
|
||
|
|
||
|
if ('function' !== typeof callback) {
|
||
|
throw new Error('A callback function is required');
|
||
|
}
|
||
|
if (!access_token || typeof access_token !== 'string') {
|
||
|
return callback(new Error('Invalid token'));
|
||
|
}
|
||
|
|
||
|
var _this = this;
|
||
|
var protocol = 'https:';
|
||
|
var domain = this._domain;
|
||
|
var endpoint = '/userinfo';
|
||
|
var url = joinUrl(protocol, domain, endpoint);
|
||
|
|
||
|
var fail = function (status, description) {
|
||
|
var error = new Error(status + ': ' + (description || ''));
|
||
|
|
||
|
// These two properties are added for compatibility with old versions (no Error instance was returned)
|
||
|
error.error = status;
|
||
|
error.error_description = description;
|
||
|
|
||
|
callback(error);
|
||
|
};
|
||
|
|
||
|
return reqwest({
|
||
|
url: same_origin(protocol, domain) ? endpoint : url,
|
||
|
method: 'post',
|
||
|
type: 'json',
|
||
|
crossOrigin: !same_origin(protocol, domain),
|
||
|
headers: {
|
||
|
'Authorization': 'Bearer ' + access_token
|
||
|
}
|
||
|
}).fail(function (err) {
|
||
|
fail(err.status, err.responseText);
|
||
|
}).then(function (userinfo) {
|
||
|
callback(null, userinfo);
|
||
|
});
|
||
|
|
||
|
};
|
||
|
|
||
|
/**
|
||
|
* Get profile data by `id_token`
|
||
|
*
|
||
|
* @param {String} id_token
|
||
|
* @param {Function} callback
|
||
|
* @method getProfile
|
||
|
*/
|
||
|
|
||
|
Auth0.prototype.getProfile = function (id_token, callback) {
|
||
|
if ('function' !== typeof callback) {
|
||
|
throw new Error('A callback function is required');
|
||
|
}
|
||
|
if (!id_token || typeof id_token !== 'string') {
|
||
|
return callback(new Error('Invalid token'));
|
||
|
}
|
||
|
|
||
|
this._getUserInfo(this.decodeJwt(id_token), id_token, callback);
|
||
|
};
|
||
|
|
||
|
/**
|
||
|
* Validate a user
|
||
|
*
|
||
|
* @param {Object} options
|
||
|
* @param {Function} callback
|
||
|
* @method validateUser
|
||
|
*/
|
||
|
|
||
|
Auth0.prototype.validateUser = function (options, callback) {
|
||
|
var protocol = 'https:';
|
||
|
var domain = this._domain;
|
||
|
var endpoint = '/public/api/users/validate_userpassword';
|
||
|
var url = joinUrl(protocol, domain, endpoint);
|
||
|
|
||
|
var query = xtend(
|
||
|
options,
|
||
|
{
|
||
|
client_id: this._clientID,
|
||
|
username: trim(options.username || options.email || '')
|
||
|
});
|
||
|
|
||
|
if (this._useJSONP) {
|
||
|
return jsonp(url + '?' + qs.stringify(query), jsonpOpts, function (err, resp) {
|
||
|
if (err) {
|
||
|
return callback(err);
|
||
|
}
|
||
|
if('error' in resp && resp.status !== 404) {
|
||
|
return callback(new Error(resp.error));
|
||
|
}
|
||
|
callback(null, resp.status === 200);
|
||
|
});
|
||
|
}
|
||
|
|
||
|
reqwest({
|
||
|
url: same_origin(protocol, domain) ? endpoint : url,
|
||
|
method: 'post',
|
||
|
type: 'text',
|
||
|
data: query,
|
||
|
crossOrigin: !same_origin(protocol, domain),
|
||
|
error: function (err) {
|
||
|
if (err.status !== 404) { return callback(new Error(err.responseText)); }
|
||
|
callback(null, false);
|
||
|
},
|
||
|
success: function (resp) {
|
||
|
callback(null, resp.status === 200);
|
||
|
}
|
||
|
});
|
||
|
};
|
||
|
|
||
|
/**
|
||
|
* Decode Json Web Token
|
||
|
*
|
||
|
* @param {String} jwt
|
||
|
* @method decodeJwt
|
||
|
*/
|
||
|
|
||
|
Auth0.prototype.decodeJwt = function (jwt) {
|
||
|
var encoded = jwt && jwt.split('.')[1];
|
||
|
return json_parse(Base64Url.decode(encoded));
|
||
|
};
|
||
|
|
||
|
/**
|
||
|
* Given the hash (or a query) of an URL returns a dictionary with only relevant
|
||
|
* authentication information. If succeeds it will return the following fields:
|
||
|
* `profile`, `id_token`, `access_token` and `state`. In case of error, it will
|
||
|
* return `error` and `error_description`.
|
||
|
*
|
||
|
* @method parseHash
|
||
|
* @param {String} [hash=window.location.hash] URL to be parsed
|
||
|
* @example
|
||
|
* var auth0 = new Auth0({...});
|
||
|
*
|
||
|
* // Returns {profile: {** decoded id token **}, state: "good"}
|
||
|
* auth0.parseHash('#id_token=.....&state=good&foo=bar');
|
||
|
*
|
||
|
* // Returns {error: "invalid_credentials", error_description: undefined}
|
||
|
* auth0.parseHash('#error=invalid_credentials');
|
||
|
*
|
||
|
* // Returns {error: "invalid_credentials", error_description: undefined}
|
||
|
* auth0.parseHash('?error=invalid_credentials');
|
||
|
*
|
||
|
*/
|
||
|
|
||
|
Auth0.prototype.parseHash = function (hash, options) {
|
||
|
options = options || {};
|
||
|
hash = hash || window.location.hash;
|
||
|
hash = hash.replace(/^#?\/?/, '');
|
||
|
var parsed_qs = qs.parse(hash);
|
||
|
|
||
|
if (parsed_qs.hasOwnProperty('error')) {
|
||
|
var err = {
|
||
|
error: parsed_qs.error,
|
||
|
error_description: parsed_qs.error_description
|
||
|
};
|
||
|
|
||
|
if (parsed_qs.state) {
|
||
|
err.state = parsed_qs.state;
|
||
|
}
|
||
|
|
||
|
return err;
|
||
|
}
|
||
|
|
||
|
if (!parsed_qs.hasOwnProperty('access_token')
|
||
|
&& !parsed_qs.hasOwnProperty('id_token')
|
||
|
&& !parsed_qs.hasOwnProperty('refresh_token')) {
|
||
|
return null;
|
||
|
}
|
||
|
|
||
|
var prof;
|
||
|
|
||
|
if (parsed_qs.id_token) {
|
||
|
var invalidJwt = function (error) {
|
||
|
var err = {
|
||
|
error: 'invalid_token',
|
||
|
error_description: error
|
||
|
};
|
||
|
return err;
|
||
|
};
|
||
|
|
||
|
prof = this.decodeJwt(parsed_qs.id_token);
|
||
|
|
||
|
// aud should be the clientID
|
||
|
var audiences = is_array(prof.aud) ? prof.aud : [ prof.aud ];
|
||
|
if (index_of(audiences, this._clientID) === -1) {
|
||
|
return invalidJwt(
|
||
|
'The clientID configured (' + this._clientID + ') does not match with the clientID set in the token (' + audiences.join(', ') + ').');
|
||
|
}
|
||
|
|
||
|
// iss should be the Auth0 domain (i.e.: https://contoso.auth0.com/)
|
||
|
if (prof.iss && prof.iss !== this._token_issuer) {
|
||
|
return invalidJwt(
|
||
|
'The domain configured (' + this._token_issuer + ') does not match with the domain set in the token (' + prof.iss + ').');
|
||
|
}
|
||
|
|
||
|
var nonce;
|
||
|
|
||
|
if (options.nonce) {
|
||
|
nonce = options.nonce;
|
||
|
} else if (window.localStorage) {
|
||
|
try {
|
||
|
nonce = window.localStorage.getItem('com.auth0.auth.nonce');
|
||
|
window.localStorage.removeItem('com.auth0.auth.nonce');
|
||
|
} catch(e) {
|
||
|
// will fail because nonce is undefined
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if ((nonce || prof.nonce) && prof.nonce !== nonce) {
|
||
|
return invalidJwt('The nonce does not match.');
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return {
|
||
|
accessToken: parsed_qs.access_token,
|
||
|
idToken: parsed_qs.id_token,
|
||
|
idTokenPayload: prof,
|
||
|
refreshToken: parsed_qs.refresh_token,
|
||
|
state: parsed_qs.state
|
||
|
};
|
||
|
};
|
||
|
|
||
|
/**
|
||
|
* Signup
|
||
|
*
|
||
|
* @param {Object} options Signup Options
|
||
|
* @param {String} email New user email
|
||
|
* @param {String} password New user password
|
||
|
*
|
||
|
* @param {Function} callback
|
||
|
* @method signup
|
||
|
*/
|
||
|
|
||
|
Auth0.prototype.signup = function (options, callback) {
|
||
|
var _this = this;
|
||
|
|
||
|
var opts = {
|
||
|
client_id: this._clientID,
|
||
|
redirect_uri: this._getCallbackURL(options),
|
||
|
email: trim(options.email || options.username || '')
|
||
|
};
|
||
|
|
||
|
if (typeof options.username === 'string') {
|
||
|
opts.username = trim(options.username);
|
||
|
}
|
||
|
|
||
|
var query = xtend(this._getMode(options), options, opts);
|
||
|
|
||
|
this._configureOfflineMode(query);
|
||
|
|
||
|
// TODO Change this to a property named 'disableSSO' for consistency.
|
||
|
// By default, options.sso is true
|
||
|
if (!checkIfSet(options, 'sso')) {
|
||
|
options.sso = true;
|
||
|
}
|
||
|
|
||
|
if (!checkIfSet(options, 'auto_login')) {
|
||
|
options.auto_login = true;
|
||
|
}
|
||
|
|
||
|
var popup;
|
||
|
|
||
|
var will_popup = options.auto_login && options.popup
|
||
|
&& (!this._getCallbackOnLocationHash(options) || options.sso);
|
||
|
|
||
|
if (will_popup) {
|
||
|
popup = this._buildPopupWindow(options);
|
||
|
}
|
||
|
|
||
|
function success () {
|
||
|
if (options.auto_login) {
|
||
|
return _this.login(options, callback);
|
||
|
}
|
||
|
|
||
|
if ('function' === typeof callback) {
|
||
|
return callback();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
function fail (status, resp) {
|
||
|
var error = new LoginError(status, resp);
|
||
|
|
||
|
// when failed we want the popup closed if opened
|
||
|
if (popup && 'function' === typeof popup.kill) {
|
||
|
popup.kill();
|
||
|
}
|
||
|
|
||
|
if ('function' === typeof callback) {
|
||
|
return callback(error);
|
||
|
}
|
||
|
|
||
|
throw error;
|
||
|
}
|
||
|
|
||
|
var protocol = 'https:';
|
||
|
var domain = this._domain;
|
||
|
var endpoint = '/dbconnections/signup';
|
||
|
var url = joinUrl(protocol, domain, endpoint);
|
||
|
|
||
|
if (this._useJSONP) {
|
||
|
return jsonp(url + '?' + qs.stringify(query), jsonpOpts, function (err, resp) {
|
||
|
if (err) {
|
||
|
return fail(0, err);
|
||
|
}
|
||
|
|
||
|
return resp.status == 200 ? success() :
|
||
|
fail(resp.status, resp.err || resp.error);
|
||
|
});
|
||
|
}
|
||
|
|
||
|
reqwest({
|
||
|
url: same_origin(protocol, domain) ? endpoint : url,
|
||
|
method: 'post',
|
||
|
type: 'html',
|
||
|
data: query,
|
||
|
success: success,
|
||
|
crossOrigin: !same_origin(protocol, domain),
|
||
|
error: function (err) {
|
||
|
fail(err.status, err.responseText);
|
||
|
}
|
||
|
});
|
||
|
};
|
||
|
|
||
|
/**
|
||
|
* Change password
|
||
|
*
|
||
|
* @param {Object} options
|
||
|
* @param {Function} callback
|
||
|
* @method changePassword
|
||
|
*/
|
||
|
|
||
|
Auth0.prototype.changePassword = function (options, callback) {
|
||
|
var query = {
|
||
|
client_id: this._clientID,
|
||
|
connection: options.connection,
|
||
|
email: trim(options.email || '')
|
||
|
};
|
||
|
|
||
|
if (typeof options.password === "string") {
|
||
|
query.password = options.password;
|
||
|
}
|
||
|
|
||
|
function fail (status, resp) {
|
||
|
var error = new LoginError(status, resp);
|
||
|
if (callback) {
|
||
|
return callback(error);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
var protocol = 'https:';
|
||
|
var domain = this._domain;
|
||
|
var endpoint = '/dbconnections/change_password';
|
||
|
var url = joinUrl(protocol, domain, endpoint);
|
||
|
|
||
|
if (this._useJSONP) {
|
||
|
return jsonp(url + '?' + qs.stringify(query), jsonpOpts, function (err, resp) {
|
||
|
if (err) {
|
||
|
return fail(0, err);
|
||
|
}
|
||
|
return resp.status == 200 ?
|
||
|
callback(null, resp.message) :
|
||
|
fail(resp.status, resp.err || resp.error);
|
||
|
});
|
||
|
}
|
||
|
|
||
|
reqwest({
|
||
|
url: same_origin(protocol, domain) ? endpoint : url,
|
||
|
method: 'post',
|
||
|
type: 'html',
|
||
|
data: query,
|
||
|
crossOrigin: !same_origin(protocol, domain),
|
||
|
error: function (err) {
|
||
|
fail(err.status, err.responseText);
|
||
|
},
|
||
|
success: function (r) {
|
||
|
callback(null, r);
|
||
|
}
|
||
|
});
|
||
|
};
|
||
|
|
||
|
/**
|
||
|
* Builds query string to be passed to /authorize based on dict key and values.
|
||
|
*
|
||
|
* @param {Array} args
|
||
|
* @param {Array} blacklist
|
||
|
* @private
|
||
|
*/
|
||
|
|
||
|
Auth0.prototype._buildAuthorizeQueryString = function (args, blacklist) {
|
||
|
var query = this._buildAuthorizationParameters(args, blacklist);
|
||
|
return qs.stringify(query);
|
||
|
};
|
||
|
|
||
|
/**
|
||
|
* Builds parameter dictionary to be passed to /authorize based on dict key and values.
|
||
|
*
|
||
|
* @param {Array} args
|
||
|
* @param {Array} blacklist
|
||
|
* @private
|
||
|
*/
|
||
|
|
||
|
Auth0.prototype._buildAuthorizationParameters = function(args, blacklist) {
|
||
|
var query = xtend.apply(null, args);
|
||
|
|
||
|
// Adds offline mode to the query
|
||
|
this._configureOfflineMode(query);
|
||
|
|
||
|
// Adds client SDK information (when enabled)
|
||
|
if ( this._sendClientInfo ) query['auth0Client'] = this._getClientInfoString();
|
||
|
|
||
|
// Elements to filter from query string
|
||
|
blacklist = blacklist || ['popup', 'popupOptions'];
|
||
|
|
||
|
var i, key;
|
||
|
|
||
|
for (i = 0; i < blacklist.length; i++) {
|
||
|
key = blacklist[i];
|
||
|
delete query[key];
|
||
|
}
|
||
|
|
||
|
if (query.connection_scope && is_array(query.connection_scope)){
|
||
|
query.connection_scope = query.connection_scope.join(',');
|
||
|
}
|
||
|
|
||
|
return query;
|
||
|
};
|
||
|
|
||
|
Auth0.prototype._buildAuthorizeUrl = function(options) {
|
||
|
var constructorOptions = {};
|
||
|
|
||
|
if (this._scope) {
|
||
|
constructorOptions.scope = this._scope;
|
||
|
}
|
||
|
|
||
|
if (this._audience) {
|
||
|
constructorOptions.audience = this._audience;
|
||
|
}
|
||
|
|
||
|
|
||
|
var qs = [
|
||
|
this._getMode(options),
|
||
|
constructorOptions,
|
||
|
options,
|
||
|
{
|
||
|
client_id: this._clientID,
|
||
|
redirect_uri: this._getCallbackURL(options)
|
||
|
}
|
||
|
];
|
||
|
|
||
|
var query = this._buildAuthorizeQueryString(qs);
|
||
|
|
||
|
return joinUrl('https:', this._domain, '/authorize?' + query);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Login user
|
||
|
*
|
||
|
* @param {Object} options
|
||
|
* @param {Function} callback
|
||
|
* @method login
|
||
|
*/
|
||
|
|
||
|
Auth0.prototype.login = Auth0.prototype.signin = function (options, callback) {
|
||
|
// TODO Change this to a property named 'disableSSO' for consistency.
|
||
|
// By default, options.sso is true
|
||
|
if (!checkIfSet(options, 'sso')) {
|
||
|
options.sso = true;
|
||
|
}
|
||
|
|
||
|
if (this._responseType.indexOf('id_token') > -1 && !options.nonce) {
|
||
|
if (typeof options.passcode === 'undefined' && (
|
||
|
((typeof options.username !== 'undefined' || typeof options.email !== 'undefined') && !callback) ||
|
||
|
(typeof options.username === 'undefined' && typeof options.email === 'undefined')
|
||
|
) ) {
|
||
|
|
||
|
if (window.localStorage) {
|
||
|
var nonce = nonceGenerator.randomString(16);
|
||
|
if (nonce) {
|
||
|
try {
|
||
|
options.nonce = nonce;
|
||
|
window.localStorage.setItem('com.auth0.auth.nonce', nonce);
|
||
|
}
|
||
|
catch(e) {
|
||
|
options.nonce = undefined;
|
||
|
}
|
||
|
}
|
||
|
} else {
|
||
|
throw new Error('Unable to generate and store nonce to request id_token. Please provide a nonce value via options');
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (typeof options.passcode !== 'undefined') {
|
||
|
return this.loginWithPasscode(options, callback);
|
||
|
}
|
||
|
|
||
|
if (typeof options.username !== 'undefined' ||
|
||
|
typeof options.email !== 'undefined') {
|
||
|
return this.loginWithUsernamePassword(options, callback);
|
||
|
}
|
||
|
|
||
|
if (!!window.cordova || !!window.electron) {
|
||
|
return this.loginPhonegap(options, callback);
|
||
|
}
|
||
|
|
||
|
if (!!options.popup && this._getCallbackOnLocationHash(options)) {
|
||
|
return this.loginWithPopup(options, callback);
|
||
|
}
|
||
|
|
||
|
if (!options.nonce && this._responseType.indexOf('id_token') > -1) {
|
||
|
throw new Error('nonce is mandatory');
|
||
|
}
|
||
|
|
||
|
this._authorize(options);
|
||
|
};
|
||
|
|
||
|
Auth0.prototype._authorize = function(options) {
|
||
|
var url = this._buildAuthorizeUrl(options);
|
||
|
|
||
|
if (options.popup) {
|
||
|
this._buildPopupWindow(options, url);
|
||
|
} else {
|
||
|
this._redirect(url);
|
||
|
}
|
||
|
};
|
||
|
|
||
|
/**
|
||
|
* Compute `options.width` and `options.height` for the popup to
|
||
|
* open and return and extended object with optimal `top` and `left`
|
||
|
* position arguments for the popup windows
|
||
|
*
|
||
|
* @param {Object} options
|
||
|
* @private
|
||
|
*/
|
||
|
|
||
|
Auth0.prototype._computePopupPosition = function (options) {
|
||
|
options = options || {};
|
||
|
var width = options.width || 500;
|
||
|
var height = options.height || 600;
|
||
|
|
||
|
var screenX = typeof window.screenX !== 'undefined' ? window.screenX : window.screenLeft;
|
||
|
var screenY = typeof window.screenY !== 'undefined' ? window.screenY : window.screenTop;
|
||
|
var outerWidth = typeof window.outerWidth !== 'undefined' ? window.outerWidth : document.body.clientWidth;
|
||
|
var outerHeight = typeof window.outerHeight !== 'undefined' ? window.outerHeight : (document.body.clientHeight - 22);
|
||
|
// XXX: what is the 22?
|
||
|
|
||
|
// Use `outerWidth - width` and `outerHeight - height` for help in
|
||
|
// positioning the popup centered relative to the current window
|
||
|
var left = screenX + (outerWidth - width) / 2;
|
||
|
var top = screenY + (outerHeight - height) / 2;
|
||
|
|
||
|
return { width: width, height: height, left: left, top: top };
|
||
|
};
|
||
|
|
||
|
/**
|
||
|
* loginPhonegap method is triggered when !!window.cordova is true.
|
||
|
*
|
||
|
* @method loginPhonegap
|
||
|
* @private
|
||
|
* @param {Object} options Login options.
|
||
|
* @param {Function} callback To be called after login happened. Callback arguments
|
||
|
* should be:
|
||
|
* function (err, profile, idToken, accessToken, state)
|
||
|
*
|
||
|
* @example
|
||
|
* var auth0 = new Auth0({ clientId: '...', domain: '...'});
|
||
|
*
|
||
|
* auth0.signin({}, function (err, profile, idToken, accessToken, state) {
|
||
|
* if (err) {
|
||
|
* alert(err);
|
||
|
* return;
|
||
|
* }
|
||
|
*
|
||
|
* alert('Welcome ' + profile.name);
|
||
|
* });
|
||
|
*/
|
||
|
|
||
|
Auth0.prototype.loginPhonegap = function (options, callback) {
|
||
|
if (this._shouldAuthenticateWithCordovaPlugin(options.connection)) {
|
||
|
this._socialPhonegapLogin(options, callback);
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
var mobileCallbackURL = joinUrl('https:', this._domain, '/mobile');
|
||
|
var _this = this;
|
||
|
var qs = [
|
||
|
this._getMode(options),
|
||
|
options,
|
||
|
{
|
||
|
client_id: this._clientID,
|
||
|
redirect_uri: mobileCallbackURL
|
||
|
}
|
||
|
];
|
||
|
|
||
|
if ( this._sendClientInfo ) {
|
||
|
qs.push({ auth0Client: this._getClientInfoString() });
|
||
|
}
|
||
|
|
||
|
var query = this._buildAuthorizeQueryString(qs);
|
||
|
|
||
|
var popupUrl = joinUrl('https:', this._domain, '/authorize?' + query);
|
||
|
|
||
|
var popupOptions = xtend({location: 'yes'} ,
|
||
|
options.popupOptions);
|
||
|
|
||
|
// This wasn't send before so we don't send it now either
|
||
|
delete popupOptions.width;
|
||
|
delete popupOptions.height;
|
||
|
|
||
|
var ref = this.openWindow(popupUrl, '_blank', popupOptions);
|
||
|
var answered = false;
|
||
|
|
||
|
function errorHandler(event) {
|
||
|
if (answered) { return; }
|
||
|
answered = true;
|
||
|
ref.close();
|
||
|
callback(new Error(event.message), null);
|
||
|
}
|
||
|
|
||
|
function startHandler(event) {
|
||
|
if (answered) { return; }
|
||
|
|
||
|
if ( event.url && !(event.url.indexOf(mobileCallbackURL + '#') === 0 ||
|
||
|
event.url.indexOf(mobileCallbackURL + '?') === 0)) { return; }
|
||
|
|
||
|
var result = _this.parseHash(event.url.slice(mobileCallbackURL.length));
|
||
|
|
||
|
if (!result) {
|
||
|
answered = true;
|
||
|
ref.close();
|
||
|
callback(new Error('Error parsing hash'), null);
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
if (result.idToken) {
|
||
|
answered = true;
|
||
|
ref.close();
|
||
|
callback(null, result);
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
|
||
|
// Case where we've found an error
|
||
|
answered = true;
|
||
|
ref.close();
|
||
|
callback(new Error(result.err || result.error || 'Something went wrong'), null);
|
||
|
}
|
||
|
|
||
|
function exitHandler() {
|
||
|
if (answered) { return; }
|
||
|
|
||
|
ref.removeEventListener('loaderror', errorHandler);
|
||
|
ref.removeEventListener('loadstart', startHandler);
|
||
|
ref.removeEventListener('exit', exitHandler);
|
||
|
|
||
|
callback(new Error('Browser window closed'), null);
|
||
|
}
|
||
|
|
||
|
ref.addEventListener('loaderror', errorHandler);
|
||
|
ref.addEventListener('loadstart', startHandler);
|
||
|
ref.addEventListener('exit', exitHandler);
|
||
|
|
||
|
};
|
||
|
|
||
|
/**
|
||
|
* loginWithPopup method is triggered when login method receives a {popup: true} in
|
||
|
* the login options.
|
||
|
*
|
||
|
* @method loginWithPopup
|
||
|
* @param {Object} options Login options.
|
||
|
* @param {function} callback To be called after login happened (whether
|
||
|
* success or failure). This parameter is mandatory when
|
||
|
* option callbackOnLocationHash is truthy but should not
|
||
|
* be used when falsy.
|
||
|
* @example
|
||
|
* var auth0 = new Auth0({ clientId: '...', domain: '...', callbackOnLocationHash: true });
|
||
|
*
|
||
|
* // Error! No callback
|
||
|
* auth0.login({popup: true});
|
||
|
*
|
||
|
* // Ok!
|
||
|
* auth0.login({popup: true}, function () { });
|
||
|
*
|
||
|
* @example
|
||
|
* var auth0 = new Auth0({ clientId: '...', domain: '...'});
|
||
|
*
|
||
|
* // Ok!
|
||
|
* auth0.login({popup: true});
|
||
|
*
|
||
|
* // Error! No callback will be executed on response_type=code
|
||
|
* auth0.login({popup: true}, function () { });
|
||
|
* @private
|
||
|
*/
|
||
|
|
||
|
Auth0.prototype.loginWithPopup = function(options, callback) {
|
||
|
var _this = this;
|
||
|
|
||
|
if (!callback) {
|
||
|
throw new Error('popup mode should receive a mandatory callback');
|
||
|
}
|
||
|
|
||
|
if (!options.nonce && this._responseType.indexOf('id_token') > -1) {
|
||
|
throw new Error('nonce is mandatory');
|
||
|
}
|
||
|
|
||
|
var qs = [this._getMode(options), options, { client_id: this._clientID, owp: true }];
|
||
|
|
||
|
if (this._sendClientInfo) {
|
||
|
qs.push({ auth0Client: this._getClientInfoString() });
|
||
|
}
|
||
|
|
||
|
var query = this._buildAuthorizeQueryString(qs);
|
||
|
var popupUrl = joinUrl('https:', this._domain, '/authorize?' + query);
|
||
|
|
||
|
var popupPosition = this._computePopupPosition(options.popupOptions);
|
||
|
var popupOptions = xtend(popupPosition, options.popupOptions);
|
||
|
|
||
|
var popup = WinChan.open({
|
||
|
url: popupUrl,
|
||
|
relay_url: 'https://' + this._domain + '/relay.html',
|
||
|
window_features: stringifyPopupSettings(popupOptions)
|
||
|
}, function (err, result) {
|
||
|
// Eliminate `_current_popup` reference manually because
|
||
|
// Winchan removes `.kill()` method from window and also
|
||
|
// doesn't call `.kill()` by itself
|
||
|
_this._current_popup = null;
|
||
|
|
||
|
// Winchan always returns string errors, we wrap them inside Error objects
|
||
|
if (err) {
|
||
|
return callback(new LoginError(err), null, null, null, null, null);
|
||
|
}
|
||
|
|
||
|
// Handle edge case with generic error
|
||
|
if (!result) {
|
||
|
return callback(new LoginError('Something went wrong'), null, null, null, null, null);
|
||
|
}
|
||
|
|
||
|
// Handle profile retrieval from id_token and respond
|
||
|
if (result.access_token || result.id_token) {
|
||
|
return callback(null, _this._prepareResult(result));
|
||
|
}
|
||
|
|
||
|
// Case where the error is returned at an `err` property from the result
|
||
|
if (result.err) {
|
||
|
return callback(new LoginError(result.err.status, result.err.details || result.err), null, null, null, null, null);
|
||
|
}
|
||
|
|
||
|
// Case for sso_dbconnection_popup returning error at result.error instead of result.err
|
||
|
if (result.error) {
|
||
|
return callback(new LoginError(result.status, result.details || result), null, null, null, null, null);
|
||
|
}
|
||
|
|
||
|
// Case we couldn't match any error, we return a generic one
|
||
|
return callback(new LoginError('Something went wrong'), null, null, null, null, null);
|
||
|
});
|
||
|
|
||
|
popup.focus();
|
||
|
};
|
||
|
|
||
|
/**
|
||
|
* _shouldAuthenticateWithCordovaPlugin method checks whether Auth0 is properly configured to
|
||
|
* handle authentication of a social connnection using a phonegap plugin.
|
||
|
*
|
||
|
* @param {String} connection Name of the connection.
|
||
|
* @private
|
||
|
*/
|
||
|
|
||
|
Auth0.prototype._shouldAuthenticateWithCordovaPlugin = function(connection) {
|
||
|
var socialPlugin = this._cordovaSocialPlugins[connection];
|
||
|
return this._useCordovaSocialPlugins && !!socialPlugin;
|
||
|
};
|
||
|
|
||
|
/**
|
||
|
* _socialPhonegapLogin performs social authentication using a phonegap plugin
|
||
|
*
|
||
|
* @param {String} connection Name of the connection.
|
||
|
* @param {function} callback To be called after login happened (whether
|
||
|
* success or failure).
|
||
|
* @private
|
||
|
*/
|
||
|
|
||
|
Auth0.prototype._socialPhonegapLogin = function(options, callback) {
|
||
|
var socialAuthentication = this._cordovaSocialPlugins[options.connection];
|
||
|
var _this = this;
|
||
|
socialAuthentication(options.connection_scope, function(error, accessToken, extras) {
|
||
|
if (error) {
|
||
|
callback(error, null, null, null, null);
|
||
|
return;
|
||
|
}
|
||
|
var loginOptions = xtend({ access_token: accessToken }, options, extras);
|
||
|
_this.loginWithSocialAccessToken(loginOptions, callback);
|
||
|
});
|
||
|
};
|
||
|
|
||
|
/**
|
||
|
* _phonegapFacebookLogin performs social authentication with Facebook using phonegap-facebook-plugin
|
||
|
*
|
||
|
* @param {Object} scopes FB scopes used to login. It can be an Array of String or a single String.
|
||
|
* By default is ["public_profile"]
|
||
|
* @param {function} callback To be called after login happened (whether success or failure). It will
|
||
|
* yield the accessToken and any extra information neeeded by Auth0 API
|
||
|
* or an Error if the authentication fails. Callback should be:
|
||
|
* function (err, accessToken, extras) { }
|
||
|
* @private
|
||
|
*/
|
||
|
|
||
|
Auth0.prototype._phonegapFacebookLogin = function(scopes, callback) {
|
||
|
if (!window.facebookConnectPlugin || !window.facebookConnectPlugin.login) {
|
||
|
callback(new Error('missing plugin phonegap-facebook-plugin'), null, null);
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
var fbScopes;
|
||
|
if (scopes && is_array(scopes)){
|
||
|
fbScopes = scopes;
|
||
|
} else if (scopes) {
|
||
|
fbScopes = [scopes];
|
||
|
} else {
|
||
|
fbScopes = ['public_profile'];
|
||
|
}
|
||
|
window.facebookConnectPlugin.login(fbScopes, function (state) {
|
||
|
callback(null, state.authResponse.accessToken, {});
|
||
|
}, function(error) {
|
||
|
callback(new Error(error), null, null);
|
||
|
});
|
||
|
};
|
||
|
|
||
|
/**
|
||
|
* This method handles the scenario where a db connection is used with
|
||
|
* popup: true and sso: true.
|
||
|
*
|
||
|
* @private
|
||
|
*/
|
||
|
Auth0.prototype.loginWithUsernamePasswordAndSSO = function (options, callback) {
|
||
|
var _this = this;
|
||
|
var popupPosition = this._computePopupPosition(options.popupOptions);
|
||
|
var popupOptions = xtend(popupPosition, options.popupOptions);
|
||
|
|
||
|
if (!options.nonce && this._responseType.indexOf('id_token') > -1) {
|
||
|
throw new Error('nonce is mandatory');
|
||
|
}
|
||
|
|
||
|
var winchanOptions = {
|
||
|
url: 'https://' + this._domain + '/sso_dbconnection_popup/' + this._clientID,
|
||
|
relay_url: 'https://' + this._domain + '/relay.html',
|
||
|
window_features: stringifyPopupSettings(popupOptions),
|
||
|
popup: this._current_popup,
|
||
|
params: {
|
||
|
domain: this._domain,
|
||
|
clientID: this._clientID,
|
||
|
options: {
|
||
|
// TODO What happens with i18n?
|
||
|
username: trim(options.username || options.email || ''),
|
||
|
password: options.password,
|
||
|
connection: options.connection,
|
||
|
state: options.state,
|
||
|
scope: options.scope
|
||
|
}
|
||
|
}
|
||
|
};
|
||
|
|
||
|
if (options._csrf) {
|
||
|
winchanOptions.params.options._csrf = options._csrf;
|
||
|
}
|
||
|
|
||
|
if (options.device) {
|
||
|
winchanOptions.params.options.device = options.device;
|
||
|
}
|
||
|
|
||
|
var popup = WinChan.open(winchanOptions, function (err, result) {
|
||
|
// Eliminate `_current_popup` reference manually because
|
||
|
// Winchan removes `.kill()` method from window and also
|
||
|
// doesn't call `.kill()` by itself
|
||
|
_this._current_popup = null;
|
||
|
|
||
|
// Winchan always returns string errors, we wrap them inside Error objects
|
||
|
if (err) {
|
||
|
return callback(new LoginError(err), null, null, null, null, null);
|
||
|
}
|
||
|
|
||
|
// Handle edge case with generic error
|
||
|
if (!result) {
|
||
|
return callback(new LoginError('Something went wrong'), null, null, null, null, null);
|
||
|
}
|
||
|
|
||
|
// Handle profile retrieval from id_token and respond
|
||
|
if (result.id_token) {
|
||
|
return callback(null, _this._prepareResult(result));
|
||
|
}
|
||
|
|
||
|
// Case where the error is returned at an `err` property from the result
|
||
|
if (result.err) {
|
||
|
return callback(new LoginError(result.err.status, result.err.details || result.err), null, null, null, null, null);
|
||
|
}
|
||
|
|
||
|
// Case for sso_dbconnection_popup returning error at result.error instead of result.err
|
||
|
if (result.error) {
|
||
|
return callback(new LoginError(result.status, result.details || result), null, null, null, null, null);
|
||
|
}
|
||
|
|
||
|
// Case we couldn't match any error, we return a generic one
|
||
|
return callback(new LoginError('Something went wrong'), null, null, null, null, null);
|
||
|
});
|
||
|
|
||
|
popup.focus();
|
||
|
};
|
||
|
|
||
|
/**
|
||
|
* Login with Resource Owner (RO)
|
||
|
*
|
||
|
* @param {Object} options
|
||
|
* @param {Function} callback
|
||
|
* @method loginWithResourceOwner
|
||
|
*/
|
||
|
|
||
|
Auth0.prototype.loginWithResourceOwner = function (options, callback) {
|
||
|
var _this = this;
|
||
|
var query = xtend(
|
||
|
this._getMode(options),
|
||
|
options,
|
||
|
{
|
||
|
client_id: this._clientID,
|
||
|
username: trim(options.username || options.email || ''),
|
||
|
grant_type: 'password'
|
||
|
});
|
||
|
|
||
|
this._configureOfflineMode(query);
|
||
|
|
||
|
var protocol = 'https:';
|
||
|
var domain = this._domain;
|
||
|
var endpoint = '/oauth/ro';
|
||
|
var url = joinUrl(protocol, domain, endpoint);
|
||
|
|
||
|
if ( this._sendClientInfo && this._useJSONP ) {
|
||
|
query['auth0Client'] = this._getClientInfoString();
|
||
|
}
|
||
|
|
||
|
if (this._useJSONP) {
|
||
|
return jsonp(url + '?' + qs.stringify(query), jsonpOpts, function (err, resp) {
|
||
|
if (err) {
|
||
|
return callback(err);
|
||
|
}
|
||
|
if('error' in resp) {
|
||
|
var error = new LoginError(resp.status, resp.error);
|
||
|
return callback(error);
|
||
|
}
|
||
|
callback(null, _this._prepareResult(resp));
|
||
|
});
|
||
|
}
|
||
|
|
||
|
reqwest({
|
||
|
url: same_origin(protocol, domain) ? endpoint : url,
|
||
|
method: 'post',
|
||
|
type: 'json',
|
||
|
data: query,
|
||
|
headers: this._getClientInfoHeader(),
|
||
|
crossOrigin: !same_origin(protocol, domain),
|
||
|
success: function (resp) {
|
||
|
callback(null, _this._prepareResult(resp));
|
||
|
},
|
||
|
error: function (err) {
|
||
|
handleRequestError(err, callback);
|
||
|
}
|
||
|
});
|
||
|
};
|
||
|
|
||
|
/**
|
||
|
* Login with Social Access Token
|
||
|
*
|
||
|
* @param {Object} options
|
||
|
* @param {Function} callback
|
||
|
* @method loginWithSocialAccessToken
|
||
|
*/
|
||
|
|
||
|
Auth0.prototype.loginWithSocialAccessToken = function (options, callback) {
|
||
|
var _this = this;
|
||
|
var query = this._buildAuthorizationParameters([
|
||
|
{ scope: this._scope },
|
||
|
options,
|
||
|
{ client_id: this._clientID }
|
||
|
]);
|
||
|
|
||
|
var protocol = 'https:';
|
||
|
var domain = this._domain;
|
||
|
var endpoint = '/oauth/access_token';
|
||
|
var url = joinUrl(protocol, domain, endpoint);
|
||
|
|
||
|
if (this._useJSONP) {
|
||
|
return jsonp(url + '?' + qs.stringify(query), jsonpOpts, function (err, resp) {
|
||
|
if (err) {
|
||
|
return callback(err);
|
||
|
}
|
||
|
if('error' in resp) {
|
||
|
var error = new LoginError(resp.status, resp.error);
|
||
|
return callback(error);
|
||
|
}
|
||
|
callback(null, _this._prepareResult(resp));
|
||
|
});
|
||
|
}
|
||
|
|
||
|
reqwest({
|
||
|
url: same_origin(protocol, domain) ? endpoint : url,
|
||
|
method: 'post',
|
||
|
type: 'json',
|
||
|
data: query,
|
||
|
headers: this._getClientInfoHeader(),
|
||
|
crossOrigin: !same_origin(protocol, domain),
|
||
|
success: function (resp) {
|
||
|
callback(null, _this._prepareResult(resp));
|
||
|
},
|
||
|
error: function (err) {
|
||
|
handleRequestError(err, callback);
|
||
|
}
|
||
|
});
|
||
|
};
|
||
|
|
||
|
/**
|
||
|
* Open a popup, store the winref in the instance and return it.
|
||
|
*
|
||
|
* We usually need to call this method before any ajax transaction in order
|
||
|
* to prevent the browser to block the popup.
|
||
|
*
|
||
|
* @param {[type]} options [description]
|
||
|
* @param {Function} callback [description]
|
||
|
* @return {[type]} [description]
|
||
|
* @private
|
||
|
*/
|
||
|
|
||
|
Auth0.prototype._buildPopupWindow = function (options, url) {
|
||
|
if (this._current_popup && !this._current_popup.closed) {
|
||
|
return this._current_popup;
|
||
|
}
|
||
|
|
||
|
url = url || 'about:blank'
|
||
|
|
||
|
var _this = this;
|
||
|
var defaults = { width: 500, height: 600 };
|
||
|
var opts = xtend(defaults, options.popupOptions || {});
|
||
|
var popupOptions = stringifyPopupSettings(opts);
|
||
|
|
||
|
this._current_popup = window.open(url, 'auth0_signup_popup', popupOptions);
|
||
|
|
||
|
if (!this._current_popup) {
|
||
|
throw new Error('Popup window cannot not been created. Disable popup blocker or make sure to call Auth0 login or singup on an UI event.');
|
||
|
}
|
||
|
|
||
|
this._current_popup.kill = function () {
|
||
|
this.close();
|
||
|
_this._current_popup = null;
|
||
|
};
|
||
|
|
||
|
return this._current_popup;
|
||
|
};
|
||
|
|
||
|
/**
|
||
|
* Login with Username and Password
|
||
|
*
|
||
|
* @param {Object} options
|
||
|
* @param {Function} callback
|
||
|
* @method loginWithUsernamePassword
|
||
|
*/
|
||
|
|
||
|
Auth0.prototype.loginWithUsernamePassword = function (options, callback) {
|
||
|
// XXX: Warning: This check is whether callback arguments are
|
||
|
// fn(err) case callback.length === 1 (a redirect should be performed) vs.
|
||
|
// fn(err, profile, id_token, access_token, state) callback.length > 1 (no
|
||
|
// redirect should be performed)
|
||
|
//
|
||
|
// Note: Phonegap/Cordova:
|
||
|
// As the popup is launched using the InAppBrowser plugin the SSO cookie will
|
||
|
// be set on the InAppBrowser browser. That's why the browser where the app runs
|
||
|
// won't get the sso cookie. Therefore, we don't allow username password using
|
||
|
// popup with sso: true in Cordova/Phonegap and we default to resource owner auth.
|
||
|
if (callback && callback.length > 1 && (!options.sso || window.cordova)) {
|
||
|
return this.loginWithResourceOwner(options, callback);
|
||
|
}
|
||
|
|
||
|
var _this = this;
|
||
|
var popup;
|
||
|
|
||
|
// TODO We should deprecate this, really hacky and confuses people.
|
||
|
if (options.popup && !this._getCallbackOnLocationHash(options)) {
|
||
|
popup = this._buildPopupWindow(options);
|
||
|
}
|
||
|
|
||
|
if (!options.nonce && this._responseType.indexOf('id_token') > -1) {
|
||
|
throw new Error('nonce is mandatory');
|
||
|
}
|
||
|
|
||
|
// When a callback with more than one argument is specified and sso: true then
|
||
|
// we open a popup and do authentication there.
|
||
|
if (callback && callback.length > 1 && options.sso ) {
|
||
|
return this.loginWithUsernamePasswordAndSSO(options, callback);
|
||
|
}
|
||
|
|
||
|
var query = xtend(
|
||
|
this._getMode(options),
|
||
|
options,
|
||
|
{
|
||
|
client_id: this._clientID,
|
||
|
redirect_uri: this._getCallbackURL(options),
|
||
|
username: trim(options.username || options.email || ''),
|
||
|
tenant: this._tenant
|
||
|
});
|
||
|
|
||
|
this._configureOfflineMode(query);
|
||
|
|
||
|
var protocol = 'https:';
|
||
|
var domain = this._domain;
|
||
|
var endpoint = '/usernamepassword/login';
|
||
|
var url = joinUrl(protocol, domain, endpoint);
|
||
|
|
||
|
if (this._useJSONP) {
|
||
|
return jsonp(url + '?' + qs.stringify(query), jsonpOpts, function (err, resp) {
|
||
|
if (err) {
|
||
|
if (popup && popup.kill) { popup.kill(); }
|
||
|
return callback(err);
|
||
|
}
|
||
|
if('error' in resp) {
|
||
|
if (popup && popup.kill) { popup.kill(); }
|
||
|
var error = new LoginError(resp.status, resp.error);
|
||
|
return callback(error);
|
||
|
}
|
||
|
_this._renderAndSubmitWSFedForm(options, resp.form);
|
||
|
});
|
||
|
}
|
||
|
|
||
|
function return_error (error) {
|
||
|
if (callback) {
|
||
|
return callback(error);
|
||
|
}
|
||
|
throw error;
|
||
|
}
|
||
|
|
||
|
reqwest({
|
||
|
url: same_origin(protocol, domain) ? endpoint : url,
|
||
|
method: 'post',
|
||
|
type: 'html',
|
||
|
data: query,
|
||
|
headers: this._getClientInfoHeader(),
|
||
|
crossOrigin: !same_origin(protocol, domain),
|
||
|
success: function (resp) {
|
||
|
_this._renderAndSubmitWSFedForm(options, resp);
|
||
|
},
|
||
|
error: function (err) {
|
||
|
if (popup && popup.kill) {
|
||
|
popup.kill();
|
||
|
}
|
||
|
handleRequestError(err, return_error);
|
||
|
}
|
||
|
});
|
||
|
};
|
||
|
|
||
|
/**
|
||
|
* Login with phone number and passcode
|
||
|
*
|
||
|
* @param {Object} options
|
||
|
* @param {Function} callback
|
||
|
* @method loginWithPhoneNumber
|
||
|
*/
|
||
|
Auth0.prototype.loginWithPasscode = function (options, callback) {
|
||
|
|
||
|
if (options.email == null && options.phoneNumber == null) {
|
||
|
throw new Error('email or phoneNumber is required for authentication');
|
||
|
}
|
||
|
|
||
|
if (options.passcode == null) {
|
||
|
throw new Error('passcode is required for authentication');
|
||
|
}
|
||
|
|
||
|
options.connection = options.email == null ? 'sms' : 'email';
|
||
|
|
||
|
if (!this._shouldRedirect) {
|
||
|
options = xtend(options, {
|
||
|
username: options.email == null ? options.phoneNumber : options.email,
|
||
|
password: options.passcode,
|
||
|
sso: false
|
||
|
});
|
||
|
|
||
|
delete options.email;
|
||
|
delete options.phoneNumber;
|
||
|
delete options.passcode;
|
||
|
|
||
|
return this.loginWithResourceOwner(options, callback);
|
||
|
}
|
||
|
|
||
|
var verifyOptions = {connection: options.connection};
|
||
|
|
||
|
if (options.phoneNumber) {
|
||
|
options.phone_number = options.phoneNumber;
|
||
|
delete options.phoneNumber;
|
||
|
|
||
|
verifyOptions.phone_number = options.phone_number;
|
||
|
}
|
||
|
|
||
|
if (options.email) {
|
||
|
verifyOptions.email = options.email;
|
||
|
}
|
||
|
|
||
|
options.verification_code = options.passcode;
|
||
|
delete options.passcode;
|
||
|
|
||
|
verifyOptions.verification_code = options.verification_code;
|
||
|
|
||
|
var _this = this;
|
||
|
this._verify(verifyOptions, function(error) {
|
||
|
if (error) {
|
||
|
return callback(error);
|
||
|
}
|
||
|
_this._verify_redirect(options);
|
||
|
});
|
||
|
};
|
||
|
|
||
|
Auth0.prototype._verify = function(options, callback) {
|
||
|
var protocol = 'https:';
|
||
|
var domain = this._domain;
|
||
|
var endpoint = '/passwordless/verify';
|
||
|
var url = joinUrl(protocol, domain, endpoint);
|
||
|
|
||
|
var data = options;
|
||
|
|
||
|
if (this._useJSONP) {
|
||
|
if (this._sendClientInfo) {
|
||
|
data['auth0Client'] = this._getClientInfoString();
|
||
|
}
|
||
|
|
||
|
return jsonp(url + '?' + qs.stringify(data), jsonpOpts, function (err, resp) {
|
||
|
if (err) {
|
||
|
return callback(new Error(0 + ': ' + err.toString()));
|
||
|
}
|
||
|
// /**/ typeof __auth0jp0 === 'function' && __auth0jp0({"status":400});
|
||
|
return resp.status === 200 ? callback(null, true) : callback({status: resp.status});
|
||
|
});
|
||
|
}
|
||
|
|
||
|
return reqwest({
|
||
|
url: same_origin(protocol, domain) ? endpoint : url,
|
||
|
method: 'post',
|
||
|
headers: this._getClientInfoHeader(),
|
||
|
crossOrigin: !same_origin(protocol, domain),
|
||
|
data: data
|
||
|
})
|
||
|
.fail(function (err) {
|
||
|
try {
|
||
|
callback(JSON.parse(err.responseText));
|
||
|
} catch (e) {
|
||
|
var error = new Error(err.status + '(' + err.statusText + '): ' + err.responseText);
|
||
|
error.statusCode = err.status;
|
||
|
error.error = err.statusText;
|
||
|
error.message = err.responseText;
|
||
|
callback(error);
|
||
|
}
|
||
|
})
|
||
|
.then(function (result) {
|
||
|
callback(null, result);
|
||
|
});
|
||
|
}
|
||
|
|
||
|
Auth0.prototype._verify_redirect = function(options) {
|
||
|
var qs = [
|
||
|
this._getMode(options),
|
||
|
options,
|
||
|
{
|
||
|
client_id: this._clientID,
|
||
|
redirect_uri: this._getCallbackURL(options)
|
||
|
}
|
||
|
];
|
||
|
|
||
|
var query = this._buildAuthorizeQueryString(qs);
|
||
|
var url = joinUrl('https:', this._domain, '/passwordless/verify_redirect?' + query);
|
||
|
|
||
|
this._redirect(url);
|
||
|
};
|
||
|
|
||
|
// TODO Document me
|
||
|
Auth0.prototype.renewIdToken = function (id_token, callback) {
|
||
|
this.getDelegationToken({
|
||
|
id_token: id_token,
|
||
|
scope: 'passthrough',
|
||
|
api: 'auth0'
|
||
|
}, callback);
|
||
|
};
|
||
|
|
||
|
// TODO Document me
|
||
|
Auth0.prototype.refreshToken = function (refresh_token, callback) {
|
||
|
this.getDelegationToken({
|
||
|
refresh_token: refresh_token,
|
||
|
scope: 'passthrough',
|
||
|
api: 'auth0'
|
||
|
}, callback);
|
||
|
};
|
||
|
|
||
|
/**
|
||
|
* Get delegation token for certain addon or certain other clientId
|
||
|
*
|
||
|
* @example
|
||
|
*
|
||
|
* auth0.getDelegationToken({
|
||
|
* id_token: '<user-id-token>',
|
||
|
* target: '<app-client-id>'
|
||
|
* api_type: 'auth0'
|
||
|
* }, function (err, delegationResult) {
|
||
|
* if (err) return console.log(err.message);
|
||
|
* // Do stuff with delegation token
|
||
|
* expect(delegationResult.id_token).to.exist;
|
||
|
* expect(delegationResult.token_type).to.eql('Bearer');
|
||
|
* expect(delegationResult.expires_in).to.eql(36000);
|
||
|
* });
|
||
|
*
|
||
|
* @example
|
||
|
*
|
||
|
* // get a delegation token from a Firebase API App
|
||
|
* auth0.getDelegationToken({
|
||
|
* id_token: '<user-id-token>',
|
||
|
* target: '<app-client-id>'
|
||
|
* api_type: 'firebase'
|
||
|
* }, function (err, delegationResult) {
|
||
|
* // Use your firebase token here
|
||
|
* });
|
||
|
*
|
||
|
* @method getDelegationToken
|
||
|
* @param {Object} [options]
|
||
|
* @param {String} [id_token]
|
||
|
* @param {String} [target]
|
||
|
* @param {String} [api_type]
|
||
|
* @param {Function} [callback]
|
||
|
*/
|
||
|
Auth0.prototype.getDelegationToken = function (options, callback) {
|
||
|
options = options || {};
|
||
|
|
||
|
if (!options.id_token && !options.refresh_token ) {
|
||
|
throw new Error('You must send either an id_token or a refresh_token to get a delegation token.');
|
||
|
}
|
||
|
|
||
|
var query = xtend({
|
||
|
grant_type: 'urn:ietf:params:oauth:grant-type:jwt-bearer',
|
||
|
client_id: this._clientID,
|
||
|
target: options.targetClientId || this._clientID,
|
||
|
api_type: options.api
|
||
|
}, options);
|
||
|
|
||
|
delete query.hasOwnProperty;
|
||
|
delete query.targetClientId;
|
||
|
delete query.api;
|
||
|
|
||
|
var protocol = 'https:';
|
||
|
var domain = this._domain;
|
||
|
var endpoint = '/delegation';
|
||
|
var url = joinUrl(protocol, domain, endpoint);
|
||
|
|
||
|
if (this._useJSONP) {
|
||
|
return jsonp(url + '?' + qs.stringify(query), jsonpOpts, function (err, resp) {
|
||
|
if (err) {
|
||
|
return callback(err);
|
||
|
}
|
||
|
if('error' in resp) {
|
||
|
var error = new LoginError(resp.status, resp.error_description || resp.error);
|
||
|
return callback(error);
|
||
|
}
|
||
|
callback(null, resp);
|
||
|
});
|
||
|
}
|
||
|
|
||
|
reqwest({
|
||
|
url: same_origin(protocol, domain) ? endpoint : url,
|
||
|
method: 'post',
|
||
|
type: 'json',
|
||
|
data: query,
|
||
|
crossOrigin: !same_origin(protocol, domain),
|
||
|
success: function (resp) {
|
||
|
callback(null, resp);
|
||
|
},
|
||
|
error: function (err) {
|
||
|
try {
|
||
|
callback(JSON.parse(err.responseText));
|
||
|
}
|
||
|
catch (e) {
|
||
|
var er = err;
|
||
|
var isAffectedIEVersion = isInternetExplorer() === 10 || isInternetExplorer() === 11;
|
||
|
var zeroStatus = (!er.status || er.status === 0);
|
||
|
|
||
|
// Request failed because we are offline.
|
||
|
// See: http://caniuse.com/#search=navigator.onLine
|
||
|
if (zeroStatus && !window.navigator.onLine) {
|
||
|
er = {};
|
||
|
er.status = 0;
|
||
|
er.responseText = {
|
||
|
code: 'offline'
|
||
|
};
|
||
|
// http://stackoverflow.com/questions/23229723/ie-10-11-cors-status-0
|
||
|
// XXX IE10 when a request fails in CORS returns status code 0
|
||
|
// XXX This is not handled by handleRequestError as the errors are different
|
||
|
} else if (zeroStatus && isAffectedIEVersion) {
|
||
|
er = {};
|
||
|
er.status = 401;
|
||
|
er.responseText = {
|
||
|
code: 'invalid_operation'
|
||
|
};
|
||
|
// If not IE10/11 and not offline it means that Auth0 host is unreachable:
|
||
|
// Connection Timeout or Connection Refused.
|
||
|
} else if (zeroStatus) {
|
||
|
er = {};
|
||
|
er.status = 0;
|
||
|
er.responseText = {
|
||
|
code: 'connection_refused_timeout'
|
||
|
};
|
||
|
} else {
|
||
|
er.responseText = err;
|
||
|
}
|
||
|
callback(new LoginError(er.status, er.responseText));
|
||
|
}
|
||
|
}
|
||
|
});
|
||
|
};
|
||
|
|
||
|
/**
|
||
|
* Fetches a new id_token/access_token from Auth0
|
||
|
*
|
||
|
* @example
|
||
|
*
|
||
|
* auth0.silentAuthentication({}, function(error, result) {
|
||
|
* if (error) {
|
||
|
* console.log(error);
|
||
|
* }
|
||
|
* // result.id_token
|
||
|
* });
|
||
|
*
|
||
|
* @example
|
||
|
*
|
||
|
* auth0.silentAuthentication({callbackUrl: "https://site.com/silentCallback"}, function(error, result) {
|
||
|
* if (error) {
|
||
|
* console.log(error);
|
||
|
* }
|
||
|
* // result.id_token
|
||
|
* });
|
||
|
*
|
||
|
* @method silentAutnetication
|
||
|
* @param {Object} options
|
||
|
* @param {function} callback
|
||
|
*/
|
||
|
Auth0.prototype.silentAuthentication = function (options, callback) {
|
||
|
var usePostMessage = options.usePostMessage || false;
|
||
|
|
||
|
delete options.usePostMessage;
|
||
|
|
||
|
options = xtend(options, {prompt:'none'});
|
||
|
var handler = new SilentAuthenticationHandler(this, this._buildAuthorizeUrl(options));
|
||
|
handler.login(callback, usePostMessage);
|
||
|
};
|
||
|
|
||
|
/**
|
||
|
* Trigger logout redirect with
|
||
|
* params from `query` object
|
||
|
*
|
||
|
* @example
|
||
|
*
|
||
|
* auth0.logout();
|
||
|
* // redirects to -> 'https://yourapp.auth0.com/logout'
|
||
|
*
|
||
|
* @example
|
||
|
*
|
||
|
* auth0.logout({returnTo: 'http://logout'});
|
||
|
* // redirects to -> 'https://yourapp.auth0.com/logout?returnTo=http://logout'
|
||
|
*
|
||
|
* @example
|
||
|
*
|
||
|
* auth0.logout(null, {version: 'v2'});
|
||
|
* // redirects to -> 'https://yourapp.auth0.com/v2/logout'
|
||
|
*
|
||
|
* @example
|
||
|
*
|
||
|
* auth0.logout({returnTo: 'http://logout'}, {version: 2});
|
||
|
* // redirects to -> 'https://yourapp.auth0.com/v2/logout?returnTo=http://logout'
|
||
|
*
|
||
|
* @method logout
|
||
|
* @param {Object} query
|
||
|
*/
|
||
|
|
||
|
Auth0.prototype.logout = function (query, options) {
|
||
|
var pathName = '/logout';
|
||
|
options = options || {};
|
||
|
|
||
|
if (options.version == 'v2') {
|
||
|
pathName = '/v2' + pathName
|
||
|
}
|
||
|
|
||
|
var url = joinUrl('https:', this._domain, pathName);
|
||
|
|
||
|
if (query) {
|
||
|
url += '?' + qs.stringify(query);
|
||
|
}
|
||
|
|
||
|
this._redirect(url);
|
||
|
};
|
||
|
|
||
|
/**
|
||
|
* Get single sign on Data
|
||
|
*
|
||
|
* @example
|
||
|
*
|
||
|
* auth0.getSSOData(function (err, ssoData) {
|
||
|
* if (err) return console.log(err.message);
|
||
|
* expect(ssoData.sso).to.exist;
|
||
|
* });
|
||
|
*
|
||
|
* @example
|
||
|
*
|
||
|
* auth0.getSSOData(false, fn);
|
||
|
*
|
||
|
* @method getSSOData
|
||
|
* @param {Boolean} withActiveDirectories
|
||
|
* @param {Function} cb
|
||
|
*/
|
||
|
|
||
|
Auth0.prototype.getSSOData = function (withActiveDirectories, cb) {
|
||
|
if (typeof withActiveDirectories === 'function') {
|
||
|
cb = withActiveDirectories;
|
||
|
withActiveDirectories = false;
|
||
|
}
|
||
|
|
||
|
var noResult = {sso: false};
|
||
|
|
||
|
if (this._useJSONP) {
|
||
|
var error = new Error("The SSO data can't be obtained using JSONP");
|
||
|
setTimeout(function() { cb(error, noResult) }, 0);
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
var protocol = 'https:';
|
||
|
var domain = this._domain;
|
||
|
var endpoint = '/user/ssodata';
|
||
|
var url = joinUrl(protocol, domain, endpoint);
|
||
|
var sameOrigin = same_origin(protocol, domain);
|
||
|
var data = {};
|
||
|
|
||
|
if (withActiveDirectories) {
|
||
|
data = {ldaps: 1, client_id: this._clientID};
|
||
|
}
|
||
|
|
||
|
return reqwest({
|
||
|
url: sameOrigin ? endpoint : url,
|
||
|
method: 'get',
|
||
|
type: 'json',
|
||
|
data: data,
|
||
|
crossOrigin: !sameOrigin,
|
||
|
withCredentials: !sameOrigin,
|
||
|
timeout: 3000
|
||
|
}).fail(function(err) {
|
||
|
var error = new Error("There was an error in the request that obtains the user's SSO data.");
|
||
|
error.cause = err;
|
||
|
cb(error, noResult);
|
||
|
}).then(function(resp) {
|
||
|
cb(null, resp);
|
||
|
});
|
||
|
};
|
||
|
|
||
|
/**
|
||
|
* Get all configured connections for a client
|
||
|
*
|
||
|
* @method getConnections
|
||
|
* @param {Function} callback
|
||
|
* @deprecated This method is deprecated. If you need to get the connections please use Management API https://auth0.com/docs/api/management/v2#!/Connections/get_connections
|
||
|
*/
|
||
|
|
||
|
Auth0.prototype.getConnections = function (callback) {
|
||
|
warn('getConnections is deprecated and will be removed shortly. Please use Management API endpoint /connections to list the connections');
|
||
|
return jsonp('https://' + this._domain + '/public/api/' + this._clientID + '/connections', jsonpOpts, callback);
|
||
|
};
|
||
|
|
||
|
/**
|
||
|
* Send email or SMS to do passwordless authentication
|
||
|
*
|
||
|
* @example
|
||
|
* // To send an email
|
||
|
* auth0.startPasswordless({email: 'foo@bar.com'}, function (err, result) {
|
||
|
* if (err) return console.log(err.error_description);
|
||
|
* console.log(result);
|
||
|
* });
|
||
|
*
|
||
|
* @example
|
||
|
* // To send a SMS
|
||
|
* auth0.startPasswordless({phoneNumber: '+14251112222'}, function (err, result) {
|
||
|
* if (err) return console.log(err.error_description);
|
||
|
* console.log(result);
|
||
|
* });
|
||
|
*
|
||
|
* @method startPasswordless
|
||
|
* @param {Object} options
|
||
|
* @param {Function} callback
|
||
|
*/
|
||
|
|
||
|
Auth0.prototype.startPasswordless = function (options, callback) {
|
||
|
if ('object' !== typeof options) {
|
||
|
throw new Error('An options object is required');
|
||
|
}
|
||
|
if ('function' !== typeof callback) {
|
||
|
throw new Error('A callback function is required');
|
||
|
}
|
||
|
if (!options.email && !options.phoneNumber) {
|
||
|
throw new Error('An `email` or a `phoneNumber` is required.');
|
||
|
}
|
||
|
|
||
|
var protocol = 'https:';
|
||
|
var domain = this._domain;
|
||
|
var endpoint = '/passwordless/start';
|
||
|
var url = joinUrl(protocol, domain, endpoint);
|
||
|
|
||
|
var data = {client_id: this._clientID};
|
||
|
if (options.email) {
|
||
|
data.email = options.email;
|
||
|
data.connection = 'email';
|
||
|
if (options.authParams) {
|
||
|
data.authParams = options.authParams;
|
||
|
}
|
||
|
|
||
|
if (!options.send || options.send === "link") {
|
||
|
if (!data.authParams) {
|
||
|
data.authParams = {};
|
||
|
}
|
||
|
|
||
|
data.authParams.redirect_uri = options.callbackURL || this._callbackURL;
|
||
|
data.authParams.response_type = this._getResponseType(options);
|
||
|
}
|
||
|
|
||
|
if (options.send) {
|
||
|
data.send = options.send;
|
||
|
}
|
||
|
} else {
|
||
|
data.phone_number = options.phoneNumber;
|
||
|
data.connection = 'sms';
|
||
|
}
|
||
|
|
||
|
if (this._useJSONP) {
|
||
|
if (this._sendClientInfo) {
|
||
|
data['auth0Client'] = this._getClientInfoString();
|
||
|
}
|
||
|
|
||
|
return jsonp(url + '?' + qs.stringify(data), jsonpOpts, function (err, resp) {
|
||
|
if (err) {
|
||
|
return callback(new Error(0 + ': ' + err.toString()));
|
||
|
}
|
||
|
return resp.status === 200 ? callback(null, true) : callback(resp.err || resp.error);
|
||
|
});
|
||
|
}
|
||
|
|
||
|
return reqwest({
|
||
|
url: same_origin(protocol, domain) ? endpoint : url,
|
||
|
method: 'post',
|
||
|
type: 'json',
|
||
|
headers: this._getClientInfoHeader(),
|
||
|
crossOrigin: !same_origin(protocol, domain),
|
||
|
data: data
|
||
|
})
|
||
|
.fail(function (err) {
|
||
|
try {
|
||
|
callback(JSON.parse(err.responseText));
|
||
|
} catch (e) {
|
||
|
var error = new Error(err.status + '(' + err.statusText + '): ' + err.responseText);
|
||
|
error.statusCode = err.status;
|
||
|
error.error = err.statusText;
|
||
|
error.message = err.responseText;
|
||
|
callback(error);
|
||
|
}
|
||
|
})
|
||
|
.then(function (result) {
|
||
|
callback(null, result);
|
||
|
});
|
||
|
};
|
||
|
|
||
|
Auth0.prototype.requestMagicLink = function(attrs, cb) {
|
||
|
return this.startPasswordless(attrs, cb);
|
||
|
};
|
||
|
|
||
|
Auth0.prototype.requestEmailCode = function(attrs, cb) {
|
||
|
attrs.send = "code";
|
||
|
return this.startPasswordless(attrs, cb);
|
||
|
};
|
||
|
|
||
|
Auth0.prototype.verifyEmailCode = function(attrs, cb) {
|
||
|
attrs.passcode = attrs.code;
|
||
|
delete attrs.code;
|
||
|
return this.login(attrs, cb);
|
||
|
};
|
||
|
|
||
|
Auth0.prototype.requestSMSCode = function(attrs, cb) {
|
||
|
return this.startPasswordless(attrs, cb);
|
||
|
};
|
||
|
|
||
|
Auth0.prototype.verifySMSCode = function(attrs, cb) {
|
||
|
attrs.passcode = attrs.code;
|
||
|
delete attrs.code;
|
||
|
return this.login(attrs, cb);
|
||
|
};
|
||
|
|
||
|
/**
|
||
|
* Returns the ISO 3166-1 code for the country where the request is
|
||
|
* originating.
|
||
|
*
|
||
|
* Fails if the request has to be made using JSONP.
|
||
|
*
|
||
|
* @private
|
||
|
*/
|
||
|
Auth0.prototype.getUserCountry = function(cb) {
|
||
|
var protocol = 'https:';
|
||
|
var domain = this._domain;
|
||
|
var endpoint = "/user/geoloc/country";
|
||
|
var url = joinUrl(protocol, domain, endpoint);
|
||
|
|
||
|
if (this._useJSONP) {
|
||
|
var error = new Error("The user's country can't be obtained using JSONP");
|
||
|
setTimeout(function() { cb(error) }, 0);
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
reqwest({
|
||
|
url: same_origin(protocol, domain) ? endpoint : url,
|
||
|
method: "get",
|
||
|
type: "json",
|
||
|
headers: this._getClientInfoHeader(),
|
||
|
crossOrigin: !same_origin(protocol, domain),
|
||
|
success: function(resp) {
|
||
|
cb(null, resp.country_code)
|
||
|
},
|
||
|
error: function(err) {
|
||
|
var error = new Error("There was an error in the request that obtains the user's country");
|
||
|
error.cause = err;
|
||
|
cb(error);
|
||
|
}
|
||
|
});
|
||
|
}
|
||
|
|
||
|
Auth0.prototype._prepareResult = function(result) {
|
||
|
if (!result || typeof result !== "object") {
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
var decodedIdToken = result.id_token ? this.decodeJwt(result.id_token) : undefined;
|
||
|
|
||
|
return {
|
||
|
accessToken: result.access_token,
|
||
|
idToken: result.id_token,
|
||
|
idTokenPayload: result.profile || decodedIdToken,
|
||
|
refreshToken: result.refresh_token,
|
||
|
state: result.state
|
||
|
};
|
||
|
}
|
||
|
|
||
|
Auth0.prototype._parseResponseType = function(opts, setFlags) {
|
||
|
if (!opts) opts = {};
|
||
|
|
||
|
if (setFlags
|
||
|
&& !this._providedResponseOptions
|
||
|
&& opts.hasOwnProperty("callbackOnLocationHash")) {
|
||
|
this._providedCallbackOnLocationHash = true;
|
||
|
}
|
||
|
|
||
|
if (setFlags
|
||
|
&& !this._providedCallbackOnLocationHash
|
||
|
&& opts.hasOwnProperty("responseType")) {
|
||
|
this._providedResponseOptions = true;
|
||
|
}
|
||
|
|
||
|
if (!this._providedCallbackOnLocationHash
|
||
|
&& !this._providedResponseOptions
|
||
|
&& opts.hasOwnProperty("callbackOnLocationHash")
|
||
|
&& opts.hasOwnProperty("responseType")) {
|
||
|
warn("The responseType option will be ignored. Both callbackOnLocationHash and responseType options were provided and they can't be used together.");
|
||
|
}
|
||
|
|
||
|
if (this._providedCallbackOnLocationHash
|
||
|
&& opts.hasOwnProperty("responseType")) {
|
||
|
warn("The responseType option will be ignored. The callbackOnLocationHash option was provided to the constructor and they can't be mixed.");
|
||
|
}
|
||
|
|
||
|
if (this._providedResponseOptions
|
||
|
&& opts.hasOwnProperty("callbackOnLocationHash")) {
|
||
|
warn("The callbackOnLocationHash option will be ignored. The responseType option was provided to the constructor and they can't be mixed.");
|
||
|
}
|
||
|
|
||
|
if (!this._providedCallbackOnLocationHash
|
||
|
&& !opts.hasOwnProperty("callbackOnLocationHash")
|
||
|
&& opts.responseType
|
||
|
&& !validResponseType(opts.responseType)) {
|
||
|
warn("The responseType option will be ignored. Its valid values are \"code\", \"id_token\", \"token\" or any combination of them.");
|
||
|
}
|
||
|
|
||
|
var result = undefined;
|
||
|
|
||
|
if (!this._providedResponseOptions
|
||
|
&& null != opts.callbackOnLocationHash) {
|
||
|
result = callbackOnLocationHashToResponseType(opts.callbackOnLocationHash);
|
||
|
}
|
||
|
|
||
|
if (!this._providedCallbackOnLocationHash
|
||
|
&& !opts.hasOwnProperty("callbackOnLocationHash")
|
||
|
&& opts.responseType
|
||
|
&& validResponseType(opts.responseType)) {
|
||
|
result = opts.responseType;
|
||
|
}
|
||
|
|
||
|
return result;
|
||
|
}
|
||
|
|
||
|
Auth0.prototype._parseResponseMode = function(opts, setFlags) {
|
||
|
if (!opts) opts = {};
|
||
|
|
||
|
if (setFlags
|
||
|
&& !this._providedCallbackOnLocationHash
|
||
|
&& opts.hasOwnProperty("responseMode")) {
|
||
|
this._providedResponseOptions = true;
|
||
|
}
|
||
|
|
||
|
if (this._providedCallbackOnLocationHash
|
||
|
&& opts.hasOwnProperty("responseMode")) {
|
||
|
warn("The responseMode option will be ignored. The callbackOnLocationHash option was provided to the constructor and they can't be mixed.");
|
||
|
}
|
||
|
|
||
|
if (!this._providedCallbackOnLocationHash
|
||
|
&& !this._providedResponseOptions
|
||
|
&& opts.hasOwnProperty("callbackOnLocationHash")
|
||
|
&& opts.hasOwnProperty("responseMode")) {
|
||
|
warn("The responseMode option will be ignored. Both callbackOnLocationHash and responseMode options were provided and they can't be used together.");
|
||
|
}
|
||
|
|
||
|
var result = undefined;
|
||
|
|
||
|
if (!this._providedCallbackOnLocationHash
|
||
|
&& opts.responseMode
|
||
|
&& !validResponseMode(opts.responseMode)) {
|
||
|
warn("The responseMode option will be ignored. Its only valid value is \"form_post\".");
|
||
|
}
|
||
|
|
||
|
if (!this._providedCallbackOnLocationHash
|
||
|
&& validResponseMode(opts.responseMode)) {
|
||
|
result = opts.responseMode;
|
||
|
}
|
||
|
|
||
|
return result;
|
||
|
}
|
||
|
|
||
|
function callbackOnLocationHashToResponseType(x) {
|
||
|
return x ? "token" : "code";
|
||
|
}
|
||
|
|
||
|
function validResponseType(str) {
|
||
|
if (typeof str !== "string") return false;
|
||
|
|
||
|
var RESPONSE_TYPES = ["code", "id_token", "token"];
|
||
|
var parts = str.split(" ");
|
||
|
|
||
|
for (var i = 0; i < parts.length; i++) {
|
||
|
if (RESPONSE_TYPES.indexOf(parts[i]) === -1) return false;
|
||
|
}
|
||
|
|
||
|
return parts.length >= 1;
|
||
|
}
|
||
|
|
||
|
function validResponseMode(str) {
|
||
|
return str === "form_post";
|
||
|
}
|
||
|
|
||
|
|
||
|
function warn(str) {
|
||
|
if (console && console.warn) {
|
||
|
console.warn(str);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Expose `Auth0` constructor
|
||
|
*/
|
||
|
|
||
|
module.exports = Auth0;
|