/** * 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: '', * target: '' * 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: '', * target: '' * 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;