GT2/GT2-Android/node_modules/auth0-js/index.js

2230 lines
61 KiB
JavaScript
Raw Normal View History

/**
* 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;