930 lines
28 KiB
JavaScript
930 lines
28 KiB
JavaScript
|
'use strict';
|
||
|
|
||
|
Object.defineProperty(exports, "__esModule", {
|
||
|
value: true
|
||
|
});
|
||
|
exports.UserManagerInstance = undefined;
|
||
|
|
||
|
var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; };
|
||
|
|
||
|
let _startLoginServerAsync = (() => {
|
||
|
var _ref = _asyncToGenerator(function* () {
|
||
|
let dfd = new Deferred();
|
||
|
|
||
|
const server = _http.default.createServer(function (req, res) {
|
||
|
if (req.method === 'POST' && req.url === '/callback') {
|
||
|
let body = '';
|
||
|
req.on('data', function (data) {
|
||
|
body += data;
|
||
|
});
|
||
|
req.on('end', function () {
|
||
|
dfd.resolve(_querystring.default.parse(body));
|
||
|
res.writeHead(200, { 'Content-Type': 'text/html' });
|
||
|
res.end(`
|
||
|
<html>
|
||
|
<head>
|
||
|
<script>
|
||
|
window.close();
|
||
|
</script>
|
||
|
</head>
|
||
|
<body>
|
||
|
Authenticated successfully! You can close this window.
|
||
|
</body>
|
||
|
</html>
|
||
|
`);
|
||
|
});
|
||
|
} else {
|
||
|
res.writeHead(200, { 'Content-Type': 'text/html' });
|
||
|
res.end(`
|
||
|
<html>
|
||
|
<head></head>
|
||
|
<body></body>
|
||
|
</html>
|
||
|
`);
|
||
|
}
|
||
|
});
|
||
|
|
||
|
server.on('clientError', function (err, socket) {
|
||
|
//eslint-disable-line
|
||
|
socket.end('HTTP/1.1 400 Bad Request\r\n\r\n');
|
||
|
});
|
||
|
|
||
|
let connections = {};
|
||
|
|
||
|
server.on('connection', function (conn) {
|
||
|
let key = conn.remoteAddress + ':' + conn.remotePort;
|
||
|
connections[key] = conn;
|
||
|
conn.on('close', function () {
|
||
|
delete connections[key];
|
||
|
});
|
||
|
});
|
||
|
|
||
|
server.destroy = function (cb) {
|
||
|
server.close(cb);
|
||
|
for (let key in connections) {
|
||
|
connections[key].destroy();
|
||
|
}
|
||
|
};
|
||
|
|
||
|
const port = yield (0, (_freeportAsync || _load_freeportAsync()).default)(11000);
|
||
|
try {
|
||
|
server.listen(port, '127.0.0.1');
|
||
|
|
||
|
return {
|
||
|
server,
|
||
|
callbackURL: `http://127.0.0.1:${port}/callback`,
|
||
|
getTokenInfoAsync: function () {
|
||
|
return dfd.promise;
|
||
|
}
|
||
|
};
|
||
|
} catch (err) {
|
||
|
throw err;
|
||
|
}
|
||
|
});
|
||
|
|
||
|
return function _startLoginServerAsync() {
|
||
|
return _ref.apply(this, arguments);
|
||
|
};
|
||
|
})();
|
||
|
|
||
|
var _lodash;
|
||
|
|
||
|
function _load_lodash() {
|
||
|
return _lodash = _interopRequireDefault(require('lodash'));
|
||
|
}
|
||
|
|
||
|
var _bluebird;
|
||
|
|
||
|
function _load_bluebird() {
|
||
|
return _bluebird = _interopRequireDefault(require('bluebird'));
|
||
|
}
|
||
|
|
||
|
var _freeportAsync;
|
||
|
|
||
|
function _load_freeportAsync() {
|
||
|
return _freeportAsync = _interopRequireDefault(require('freeport-async'));
|
||
|
}
|
||
|
|
||
|
var _http = _interopRequireDefault(require('http'));
|
||
|
|
||
|
var _querystring = _interopRequireDefault(require('querystring'));
|
||
|
|
||
|
var _opn;
|
||
|
|
||
|
function _load_opn() {
|
||
|
return _opn = _interopRequireDefault(require('opn'));
|
||
|
}
|
||
|
|
||
|
var _jsonwebtoken;
|
||
|
|
||
|
function _load_jsonwebtoken() {
|
||
|
return _jsonwebtoken = _interopRequireDefault(require('jsonwebtoken'));
|
||
|
}
|
||
|
|
||
|
var _ApiV;
|
||
|
|
||
|
function _load_ApiV() {
|
||
|
return _ApiV = _interopRequireDefault(require('./ApiV2'));
|
||
|
}
|
||
|
|
||
|
var _ApiV2;
|
||
|
|
||
|
function _load_ApiV2() {
|
||
|
return _ApiV2 = require('./ApiV2');
|
||
|
}
|
||
|
|
||
|
var _Analytics;
|
||
|
|
||
|
function _load_Analytics() {
|
||
|
return _Analytics = _interopRequireWildcard(require('./Analytics'));
|
||
|
}
|
||
|
|
||
|
var _Config;
|
||
|
|
||
|
function _load_Config() {
|
||
|
return _Config = _interopRequireDefault(require('./Config'));
|
||
|
}
|
||
|
|
||
|
var _ErrorCode;
|
||
|
|
||
|
function _load_ErrorCode() {
|
||
|
return _ErrorCode = _interopRequireDefault(require('./ErrorCode'));
|
||
|
}
|
||
|
|
||
|
var _XDLError;
|
||
|
|
||
|
function _load_XDLError() {
|
||
|
return _XDLError = _interopRequireDefault(require('./XDLError'));
|
||
|
}
|
||
|
|
||
|
var _Logger;
|
||
|
|
||
|
function _load_Logger() {
|
||
|
return _Logger = _interopRequireDefault(require('./Logger'));
|
||
|
}
|
||
|
|
||
|
var _Intercom;
|
||
|
|
||
|
function _load_Intercom() {
|
||
|
return _Intercom = _interopRequireWildcard(require('./Intercom'));
|
||
|
}
|
||
|
|
||
|
var _UserSettings;
|
||
|
|
||
|
function _load_UserSettings() {
|
||
|
return _UserSettings = _interopRequireDefault(require('./UserSettings'));
|
||
|
}
|
||
|
|
||
|
var _Utils;
|
||
|
|
||
|
function _load_Utils() {
|
||
|
return _Utils = require('./Utils');
|
||
|
}
|
||
|
|
||
|
function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj.default = obj; return newObj; } }
|
||
|
|
||
|
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
|
||
|
|
||
|
function _asyncToGenerator(fn) { return function () { var gen = fn.apply(this, arguments); return new Promise(function (resolve, reject) { function step(key, arg) { try { var info = gen[key](arg); var value = info.value; } catch (error) { reject(error); return; } if (info.done) { resolve(value); } else { return Promise.resolve(value).then(function (value) { step("next", value); }, function (err) { step("throw", err); }); } } return step("next"); }); }; }
|
||
|
|
||
|
const AUTH0_DOMAIN = 'exponent.auth0.com';
|
||
|
const AUTHENTICATION_SERVER_TIMEOUT = 1000 * 60 * 5; // 5 minutes
|
||
|
|
||
|
class UserManagerInstance {
|
||
|
constructor() {
|
||
|
this.clientID = 'o0YygTgKhOTdoWj10Yl9nY2P0SMTw38Y';
|
||
|
this.loginServer = null;
|
||
|
this.refreshSessionThreshold = 60 * 60;
|
||
|
this._currentUser = null;
|
||
|
this._getSessionLock = new (_Utils || _load_Utils()).Semaphore();
|
||
|
} // Default Client ID
|
||
|
// 1 hour
|
||
|
|
||
|
|
||
|
static getGlobalInstance() {
|
||
|
if (!__globalInstance) {
|
||
|
__globalInstance = new UserManagerInstance();
|
||
|
}
|
||
|
return __globalInstance;
|
||
|
}
|
||
|
|
||
|
initialize(clientID) {
|
||
|
if (clientID) {
|
||
|
this.clientID = clientID;
|
||
|
}
|
||
|
this.loginServer = null;
|
||
|
this._currentUser = null;
|
||
|
this._getSessionLock = new (_Utils || _load_Utils()).Semaphore();
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Logs in a user for a given login type.
|
||
|
*
|
||
|
* Valid login types are:
|
||
|
* - "user-pass": Username and password authentication
|
||
|
* - "facebook": Facebook authentication
|
||
|
* - "google": Google authentication
|
||
|
* - "github": Github authentication
|
||
|
*
|
||
|
* If the login type is "user-pass", we directly make the request to Auth0
|
||
|
* to login a user.
|
||
|
*
|
||
|
* If the login type is any of the social providers, we start a web server
|
||
|
* that can act as the receiver of the OAuth callback from the authentication
|
||
|
* process. The response we receive on that web server will be token data.
|
||
|
*/
|
||
|
loginAsync(loginType, loginArgs) {
|
||
|
var _this = this;
|
||
|
|
||
|
return _asyncToGenerator(function* () {
|
||
|
let loginOptions;
|
||
|
|
||
|
if (loginType === 'user-pass') {
|
||
|
if (!loginArgs) {
|
||
|
throw new Error(`The 'user-pass' login type requires a username and password.`);
|
||
|
}
|
||
|
loginOptions = {
|
||
|
connection: 'Username-Password-Authentication',
|
||
|
responseType: 'token',
|
||
|
sso: false,
|
||
|
username: loginArgs.username,
|
||
|
password: loginArgs.password
|
||
|
};
|
||
|
} else if (loginType === 'facebook') {
|
||
|
loginOptions = {
|
||
|
connection: 'facebook'
|
||
|
};
|
||
|
} else if (loginType === 'google') {
|
||
|
loginOptions = {
|
||
|
connection: 'google-oauth2'
|
||
|
};
|
||
|
} else if (loginType === 'github') {
|
||
|
loginOptions = {
|
||
|
connection: 'github'
|
||
|
};
|
||
|
} else {
|
||
|
throw new Error(`Invalid login type provided. Must be one of 'user-pass', 'facebook', 'google', or 'github'.`);
|
||
|
}
|
||
|
|
||
|
loginOptions = _extends({}, loginOptions, {
|
||
|
scope: 'openid offline_access username nickname',
|
||
|
// audience: 'https://exp.host',
|
||
|
responseMode: 'form_post',
|
||
|
responseType: 'token',
|
||
|
device: 'xdl'
|
||
|
});
|
||
|
|
||
|
let auth0Options = {
|
||
|
clientID: _this.clientID
|
||
|
};
|
||
|
|
||
|
if (loginType === 'user-pass') {
|
||
|
try {
|
||
|
const loginResp = yield _this._auth0LoginAsync(auth0Options, loginOptions);
|
||
|
return yield _this._getProfileAsync({
|
||
|
currentConnection: loginOptions.connection,
|
||
|
accessToken: loginResp.access_token,
|
||
|
refreshToken: loginResp.refresh_token,
|
||
|
idToken: loginResp.id_token,
|
||
|
refreshTokenClientId: _this.clientID
|
||
|
});
|
||
|
} catch (err) {
|
||
|
throw err;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Doing a social login, so start a server
|
||
|
const { server, callbackURL, getTokenInfoAsync } = yield _startLoginServerAsync();
|
||
|
|
||
|
// Kill server after 5 minutes if it hasn't already been closed
|
||
|
const destroyServerTimer = setTimeout(function () {
|
||
|
if (server.listening) {
|
||
|
server.destroy();
|
||
|
}
|
||
|
}, AUTHENTICATION_SERVER_TIMEOUT);
|
||
|
|
||
|
auth0Options = {
|
||
|
clientID: _this.clientID,
|
||
|
callbackURL
|
||
|
};
|
||
|
|
||
|
// Don't await -- we'll get response back through server
|
||
|
// This will open a browser window
|
||
|
_this._auth0LoginAsync(auth0Options, loginOptions);
|
||
|
|
||
|
// Wait for token info to come back from server
|
||
|
const tokenInfo = yield getTokenInfoAsync();
|
||
|
|
||
|
server.destroy();
|
||
|
clearTimeout(destroyServerTimer);
|
||
|
|
||
|
const profile = yield _this._getProfileAsync({
|
||
|
currentConnection: loginOptions.connection,
|
||
|
accessToken: tokenInfo.access_token,
|
||
|
refreshToken: tokenInfo.refresh_token,
|
||
|
idToken: tokenInfo.id_token,
|
||
|
refreshTokenClientId: _this.clientID
|
||
|
});
|
||
|
|
||
|
return profile;
|
||
|
})();
|
||
|
}
|
||
|
|
||
|
registerAsync(userData, user) {
|
||
|
var _this2 = this;
|
||
|
|
||
|
return _asyncToGenerator(function* () {
|
||
|
if (!user) {
|
||
|
user = yield _this2.getCurrentUserAsync();
|
||
|
}
|
||
|
|
||
|
if (user && user.kind === 'user' && user.userMetadata && user.userMetadata.onboarded) {
|
||
|
yield _this2.logoutAsync();
|
||
|
user = null;
|
||
|
}
|
||
|
|
||
|
let shouldUpdateUsernamePassword = true;
|
||
|
if (user && user.kind === 'legacyUser') {
|
||
|
// we're upgrading from an older client,
|
||
|
// so login with username/pass
|
||
|
if (userData.username && userData.password) {
|
||
|
user = yield _this2.loginAsync('user-pass', {
|
||
|
username: userData.username,
|
||
|
password: userData.password
|
||
|
});
|
||
|
}
|
||
|
shouldUpdateUsernamePassword = false;
|
||
|
}
|
||
|
|
||
|
const currentUser = user;
|
||
|
|
||
|
const shouldLinkAccount = currentUser && currentUser.currentConnection !== 'Username-Password-Authentication';
|
||
|
|
||
|
try {
|
||
|
// Create or update the profile
|
||
|
let registeredUser = yield _this2.createOrUpdateUserAsync(_extends({
|
||
|
connection: 'Username-Password-Authentication', // Always create/update username password
|
||
|
email: userData.email,
|
||
|
userMetadata: {
|
||
|
onboarded: true,
|
||
|
givenName: userData.givenName,
|
||
|
familyName: userData.familyName
|
||
|
}
|
||
|
}, shouldUpdateUsernamePassword ? { username: userData.username } : {}, shouldLinkAccount ? { emailVerified: true } : {}, shouldUpdateUsernamePassword ? { password: userData.password } : {}, currentUser && shouldLinkAccount ? {
|
||
|
forceCreate: true,
|
||
|
linkedAccountId: currentUser.userId,
|
||
|
linkedAccountConnection: currentUser.currentConnection
|
||
|
} : {}));
|
||
|
|
||
|
// if it's a new registration, or if they signed up with a social account,
|
||
|
// we need to re-log them in with their username/pass. Otherwise, they're
|
||
|
// already logged in.
|
||
|
if (shouldLinkAccount || registeredUser && (!registeredUser.loginsCount || registeredUser.loginsCount && registeredUser.loginsCount < 1)) {
|
||
|
// this is a new registration, log them in
|
||
|
registeredUser = yield _this2.loginAsync('user-pass', {
|
||
|
username: userData.username,
|
||
|
password: userData.password
|
||
|
});
|
||
|
}
|
||
|
|
||
|
return registeredUser;
|
||
|
} catch (e) {
|
||
|
throw new (_XDLError || _load_XDLError()).default((_ErrorCode || _load_ErrorCode()).default.REGISTRATION_ERROR, 'Error registering user: ' + e.message);
|
||
|
}
|
||
|
})();
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Ensure user is logged in and has a valid token.
|
||
|
*
|
||
|
* If there are any issues with the login, this method throws.
|
||
|
*/
|
||
|
ensureLoggedInAsync(options = { noTrackError: false }) {
|
||
|
var _this3 = this;
|
||
|
|
||
|
return _asyncToGenerator(function* () {
|
||
|
if ((_Config || _load_Config()).default.offline) {
|
||
|
return null;
|
||
|
}
|
||
|
|
||
|
const user = yield _this3.getCurrentUserAsync();
|
||
|
if (!user) {
|
||
|
if (yield _this3.getLegacyUserData()) {
|
||
|
throw new (_XDLError || _load_XDLError()).default((_ErrorCode || _load_ErrorCode()).default.LEGACY_ACCOUNT_ERROR, `We've updated our account system! Please login again by running \`exp login\`. Sorry for the inconvenience!`, { noTrack: options.noTrackError });
|
||
|
}
|
||
|
throw new (_XDLError || _load_XDLError()).default((_ErrorCode || _load_ErrorCode()).default.NOT_LOGGED_IN, 'Not logged in', {
|
||
|
noTrack: options.noTrackError
|
||
|
});
|
||
|
}
|
||
|
return user;
|
||
|
})();
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Get the current user based on the available token.
|
||
|
* If there is no current token, returns null.
|
||
|
*/
|
||
|
getCurrentUserAsync() {
|
||
|
var _this4 = this;
|
||
|
|
||
|
return _asyncToGenerator(function* () {
|
||
|
yield _this4._getSessionLock.acquire();
|
||
|
|
||
|
try {
|
||
|
// If user is cached and token isn't expired
|
||
|
// return the user
|
||
|
if (_this4._currentUser && !_this4._isTokenExpired(_this4._currentUser.idToken)) {
|
||
|
return _this4._currentUser;
|
||
|
}
|
||
|
|
||
|
if ((_Config || _load_Config()).default.offline) {
|
||
|
return null;
|
||
|
}
|
||
|
|
||
|
// Not cached, check for token
|
||
|
let { currentConnection, idToken, accessToken, refreshToken } = yield (_UserSettings || _load_UserSettings()).default.getAsync('auth', {});
|
||
|
|
||
|
// No tokens, no current user. Need to login
|
||
|
if (!currentConnection || !idToken || !accessToken || !refreshToken) {
|
||
|
return null;
|
||
|
}
|
||
|
|
||
|
try {
|
||
|
return yield _this4._getProfileAsync({
|
||
|
currentConnection,
|
||
|
accessToken,
|
||
|
idToken,
|
||
|
refreshToken
|
||
|
});
|
||
|
} catch (e) {
|
||
|
(_Logger || _load_Logger()).default.global.error(e);
|
||
|
// This logs us out if theres a fatal error when getting the profile with
|
||
|
// current access token
|
||
|
// However, this also logs us out if there is a network error
|
||
|
yield _this4.logoutAsync();
|
||
|
return null;
|
||
|
}
|
||
|
} finally {
|
||
|
_this4._getSessionLock.release();
|
||
|
}
|
||
|
})();
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Get legacy user data from UserSettings.
|
||
|
*/
|
||
|
getLegacyUserData() {
|
||
|
return _asyncToGenerator(function* () {
|
||
|
const legacyUsername = yield (_UserSettings || _load_UserSettings()).default.getAsync('username', null);
|
||
|
if (legacyUsername) {
|
||
|
return {
|
||
|
kind: 'legacyUser',
|
||
|
username: legacyUsername,
|
||
|
userMetadata: {
|
||
|
legacy: true,
|
||
|
needsPasswordMigration: true
|
||
|
}
|
||
|
};
|
||
|
}
|
||
|
return null;
|
||
|
})();
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Create or update a user.
|
||
|
*/
|
||
|
createOrUpdateUserAsync(userData) {
|
||
|
var _this5 = this;
|
||
|
|
||
|
return _asyncToGenerator(function* () {
|
||
|
let currentUser = _this5._currentUser;
|
||
|
if (!currentUser) {
|
||
|
// attempt to get the current user
|
||
|
currentUser = yield _this5.getCurrentUserAsync();
|
||
|
}
|
||
|
|
||
|
try {
|
||
|
const api = (_ApiV || _load_ApiV()).default.clientForUser(_this5._currentUser);
|
||
|
|
||
|
const { user: updatedUser } = yield api.postAsync('auth/createOrUpdateUser', {
|
||
|
userData: _prepareAuth0Profile(userData)
|
||
|
});
|
||
|
|
||
|
_this5._currentUser = _extends({}, _this5._currentUser || {}, _parseAuth0Profile(updatedUser));
|
||
|
return _extends({
|
||
|
kind: 'user'
|
||
|
}, _this5._currentUser);
|
||
|
} catch (e) {
|
||
|
const err = e;
|
||
|
if (err.code === 'AUTHENTICATION_ERROR') {
|
||
|
throw new Error(err.details.message);
|
||
|
}
|
||
|
throw e;
|
||
|
}
|
||
|
})();
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Logout
|
||
|
*/
|
||
|
logoutAsync() {
|
||
|
var _this6 = this;
|
||
|
|
||
|
return _asyncToGenerator(function* () {
|
||
|
if (_this6._currentUser) {
|
||
|
(_Analytics || _load_Analytics()).logEvent('Logout', {
|
||
|
username: _this6._currentUser.username
|
||
|
});
|
||
|
}
|
||
|
|
||
|
_this6._currentUser = null;
|
||
|
|
||
|
// Delete saved JWT
|
||
|
yield (_UserSettings || _load_UserSettings()).default.deleteKeyAsync('auth');
|
||
|
// Delete legacy auth
|
||
|
yield (_UserSettings || _load_UserSettings()).default.deleteKeyAsync('username');
|
||
|
|
||
|
// Logout of Intercom
|
||
|
(_Intercom || _load_Intercom()).update(null);
|
||
|
})();
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Forgot Password
|
||
|
*/
|
||
|
forgotPasswordAsync(usernameOrEmail) {
|
||
|
var _this7 = this;
|
||
|
|
||
|
return _asyncToGenerator(function* () {
|
||
|
return yield _this7._auth0ForgotPasswordAsync(usernameOrEmail);
|
||
|
})();
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Get profile given token data. Errors if token is not valid or if no
|
||
|
* user profile is returned.
|
||
|
*
|
||
|
* This method is called by all public authentication methods of `UserManager`
|
||
|
* except `logoutAsync`. Therefore, we use this method as a way to:
|
||
|
* - update the UserSettings store with the current token and user id
|
||
|
* - update UserManager._currentUser
|
||
|
* - Fire login analytics events
|
||
|
* - Update the currently assigned Intercom user
|
||
|
*
|
||
|
* Also updates UserManager._currentUser.
|
||
|
*
|
||
|
* @private
|
||
|
*/
|
||
|
_getProfileAsync({
|
||
|
currentConnection,
|
||
|
accessToken,
|
||
|
idToken,
|
||
|
refreshToken,
|
||
|
refreshTokenClientId
|
||
|
}) {
|
||
|
var _this8 = this;
|
||
|
|
||
|
return _asyncToGenerator(function* () {
|
||
|
// Attempt to grab profile from Auth0.
|
||
|
// If token is expired / getting the profile fails, use refresh token to
|
||
|
let user;
|
||
|
try {
|
||
|
const dtoken = (_jsonwebtoken || _load_jsonwebtoken()).default.decode(idToken, { complete: true });
|
||
|
const { aud } = dtoken.payload;
|
||
|
|
||
|
// If it's not a new login, refreshTokenClientId won't be set in the arguments.
|
||
|
// In this case, try to get the currentRefreshTokenClientId from UserSettings,
|
||
|
// otherwise, default back to the audience of the current id_token
|
||
|
if (!refreshTokenClientId) {
|
||
|
const { refreshTokenClientId: currentRefreshTokenClientId } = yield (_UserSettings || _load_UserSettings()).default.getAsync('auth', {});
|
||
|
if (!currentRefreshTokenClientId) {
|
||
|
refreshTokenClientId = aud; // set it to the "aud" property of the existing token
|
||
|
} else {
|
||
|
refreshTokenClientId = currentRefreshTokenClientId;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// TODO(@skevy): remove
|
||
|
if (process.env.NODE_ENV !== 'production') {
|
||
|
(_Logger || _load_Logger()).default.global.debug('REFRESH_TOKEN_CLIENT_ID', refreshTokenClientId);
|
||
|
}
|
||
|
// TODO(@skevy): remove
|
||
|
if (process.env.NODE_ENV !== 'production') {
|
||
|
(_Logger || _load_Logger()).default.global.debug('DECODED TOKEN', dtoken);
|
||
|
}
|
||
|
|
||
|
if (_this8._isTokenExpired(idToken)) {
|
||
|
// if there's less than the refresh session threshold left on the token, refresh it
|
||
|
// TODO(@skevy): remove
|
||
|
if (process.env.NODE_ENV !== 'production') {
|
||
|
(_Logger || _load_Logger()).default.global.debug('REFRESHING ID TOKEN');
|
||
|
}
|
||
|
// TODO(@skevy): remove
|
||
|
if (process.env.NODE_ENV !== 'production') {
|
||
|
(_Logger || _load_Logger()).default.global.debug('REFRESH TOKEN', refreshToken);
|
||
|
}
|
||
|
const delegationResult = yield _this8._auth0RefreshToken(refreshTokenClientId, // client id that's associated with the refresh token
|
||
|
refreshToken // refresh token to use
|
||
|
);
|
||
|
// TODO(@skevy): remove
|
||
|
if (process.env.NODE_ENV !== 'production') {
|
||
|
(_Logger || _load_Logger()).default.global.debug('SUCCESSFULLY GOT NEW ID TOKEN');
|
||
|
}
|
||
|
idToken = delegationResult.id_token;
|
||
|
// TODO(@skevy): remove
|
||
|
if (process.env.NODE_ENV !== 'production') {
|
||
|
(_Logger || _load_Logger()).default.global.debug('NEW ID TOKEN', idToken);
|
||
|
}
|
||
|
}
|
||
|
// TODO(@skevy): remove
|
||
|
if (process.env.NODE_ENV !== 'production') {
|
||
|
(_Logger || _load_Logger()).default.global.debug('ID TOKEN FOR PROFILE', idToken);
|
||
|
}
|
||
|
user = yield _this8._auth0GetProfileAsync(idToken);
|
||
|
// TODO(@skevy): remove
|
||
|
if (process.env.NODE_ENV !== 'production') {
|
||
|
(_Logger || _load_Logger()).default.global.debug('USER DATA', user);
|
||
|
}
|
||
|
if (!user) {
|
||
|
throw new Error('No user profile associated with this token');
|
||
|
}
|
||
|
} catch (e) {
|
||
|
throw e;
|
||
|
}
|
||
|
|
||
|
if (!user) {
|
||
|
throw new Error('Unable to fetch user.');
|
||
|
}
|
||
|
|
||
|
user = _extends({}, _parseAuth0Profile(user), {
|
||
|
kind: 'user',
|
||
|
currentConnection,
|
||
|
accessToken,
|
||
|
idToken,
|
||
|
refreshToken
|
||
|
});
|
||
|
|
||
|
yield (_UserSettings || _load_UserSettings()).default.mergeAsync({
|
||
|
auth: _extends({
|
||
|
userId: user.userId,
|
||
|
username: user.username,
|
||
|
currentConnection,
|
||
|
accessToken,
|
||
|
idToken,
|
||
|
refreshToken
|
||
|
}, refreshTokenClientId ? { refreshTokenClientId } : {})
|
||
|
});
|
||
|
|
||
|
yield (_UserSettings || _load_UserSettings()).default.deleteKeyAsync('username');
|
||
|
|
||
|
// If no currentUser, or currentUser.id differs from profiles
|
||
|
// user id, that means we have a new login
|
||
|
if ((!_this8._currentUser || _this8._currentUser.userId !== user.userId) && user.username && user.username !== '') {
|
||
|
(_Analytics || _load_Analytics()).logEvent('Login', {
|
||
|
userId: user.userId,
|
||
|
currentConnection: user.currentConnection,
|
||
|
username: user.username
|
||
|
});
|
||
|
|
||
|
(_Analytics || _load_Analytics()).setUserProperties(user.username, {
|
||
|
userId: user.userId,
|
||
|
currentConnection: user.currentConnection,
|
||
|
username: user.username
|
||
|
});
|
||
|
|
||
|
if (user.intercomUserHash) {
|
||
|
(_Intercom || _load_Intercom()).update(user);
|
||
|
}
|
||
|
} else {
|
||
|
(_Intercom || _load_Intercom()).update(null);
|
||
|
}
|
||
|
|
||
|
_this8._currentUser = user;
|
||
|
|
||
|
return user;
|
||
|
})();
|
||
|
}
|
||
|
|
||
|
_isTokenExpired(idToken) {
|
||
|
const dtoken = (_jsonwebtoken || _load_jsonwebtoken()).default.decode(idToken, { complete: true });
|
||
|
const { exp } = dtoken.payload;
|
||
|
// TODO(@skevy): remove
|
||
|
if (process.env.NODE_ENV !== 'production') {
|
||
|
(_Logger || _load_Logger()).default.global.debug('TOKEN EXPIRATION', exp);
|
||
|
}
|
||
|
// TODO(@skevy): remove
|
||
|
if (process.env.NODE_ENV !== 'production') {
|
||
|
(_Logger || _load_Logger()).default.global.debug('TOKEN TIME LEFT', exp - Date.now() / 1000);
|
||
|
}
|
||
|
|
||
|
return exp - Date.now() / 1000 <= this.refreshSessionThreshold;
|
||
|
}
|
||
|
|
||
|
_auth0LoginAsync(auth0Options, loginOptions) {
|
||
|
return _asyncToGenerator(function* () {
|
||
|
if (typeof window !== 'undefined' && window) {
|
||
|
const Auth0JS = _auth0JSInstanceWithOptions(auth0Options);
|
||
|
const resp = yield Auth0JS.loginAsync(loginOptions);
|
||
|
return {
|
||
|
access_token: resp.accessToken,
|
||
|
id_token: resp.idToken,
|
||
|
refresh_token: resp.refreshToken
|
||
|
};
|
||
|
}
|
||
|
|
||
|
const Auth0Node = _nodeAuth0InstanceWithOptions(auth0Options);
|
||
|
|
||
|
if (loginOptions.connection === 'Username-Password-Authentication') {
|
||
|
try {
|
||
|
return yield Auth0Node.oauth.signIn(loginOptions);
|
||
|
} catch (e) {
|
||
|
throw _formatAuth0NodeError(e);
|
||
|
}
|
||
|
} else {
|
||
|
// social
|
||
|
(0, (_opn || _load_opn()).default)(_buildAuth0SocialLoginUrl(auth0Options, loginOptions), {
|
||
|
wait: false
|
||
|
});
|
||
|
return {};
|
||
|
}
|
||
|
})();
|
||
|
}
|
||
|
|
||
|
_auth0GetProfileAsync(idToken) {
|
||
|
var _this9 = this;
|
||
|
|
||
|
return _asyncToGenerator(function* () {
|
||
|
if (typeof window !== 'undefined' && window) {
|
||
|
const Auth0JS = _auth0JSInstanceWithOptions({ clientID: _this9.clientID });
|
||
|
return yield Auth0JS.getProfileAsync(idToken);
|
||
|
}
|
||
|
|
||
|
const Auth0Node = _nodeAuth0InstanceWithOptions({
|
||
|
clientID: _this9.clientID
|
||
|
});
|
||
|
|
||
|
const profile = yield Auth0Node.tokens.getInfo(idToken);
|
||
|
return profile;
|
||
|
})();
|
||
|
}
|
||
|
|
||
|
_auth0RefreshToken(clientId, refreshToken) {
|
||
|
var _this10 = this;
|
||
|
|
||
|
return _asyncToGenerator(function* () {
|
||
|
const delegationTokenOptions = {
|
||
|
refresh_token: refreshToken,
|
||
|
api_type: 'app',
|
||
|
scope: 'openid offline_access nickname username',
|
||
|
target: _this10.clientID,
|
||
|
client_id: clientId
|
||
|
};
|
||
|
|
||
|
if (typeof window !== 'undefined' && window) {
|
||
|
const Auth0JS = _auth0JSInstanceWithOptions({
|
||
|
clientID: clientId
|
||
|
});
|
||
|
|
||
|
return yield Auth0JS.getDelegationTokenAsync(_extends({}, delegationTokenOptions));
|
||
|
}
|
||
|
|
||
|
const Auth0Node = _nodeAuth0InstanceWithOptions({
|
||
|
clientID: _this10.clientID
|
||
|
});
|
||
|
|
||
|
const delegationResult = yield Auth0Node.tokens.getDelegationToken(_extends({
|
||
|
grant_type: 'urn:ietf:params:oauth:grant-type:jwt-bearer'
|
||
|
}, delegationTokenOptions));
|
||
|
|
||
|
return delegationResult;
|
||
|
})();
|
||
|
}
|
||
|
|
||
|
_auth0ForgotPasswordAsync(usernameOrEmail) {
|
||
|
var _this11 = this;
|
||
|
|
||
|
return _asyncToGenerator(function* () {
|
||
|
if (typeof window !== 'undefined' && window) {
|
||
|
const Auth0JS = _auth0JSInstanceWithOptions({ clientID: _this11.clientID });
|
||
|
return yield Auth0JS.changePasswordAsync({
|
||
|
connection: 'Username-Password-Authentication',
|
||
|
email: usernameOrEmail
|
||
|
});
|
||
|
}
|
||
|
|
||
|
const Auth0Node = _nodeAuth0InstanceWithOptions({
|
||
|
clientID: _this11.clientID
|
||
|
});
|
||
|
|
||
|
return yield Auth0Node.database.changePassword({
|
||
|
connection: 'Username-Password-Authentication',
|
||
|
email: usernameOrEmail
|
||
|
});
|
||
|
})();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
exports.UserManagerInstance = UserManagerInstance;
|
||
|
let __globalInstance;
|
||
|
exports.default = UserManagerInstance.getGlobalInstance();
|
||
|
|
||
|
/** Private Methods **/
|
||
|
|
||
|
function _formatAuth0NodeError(e) {
|
||
|
// TODO: Fix the Auth0 js library to throw better error messages when the network fails.
|
||
|
// Auth0 returns an error object whenver Auth0 fails to make an API request.
|
||
|
// These error messages are usually well-formed when you have an invalid login or too many attempts,
|
||
|
// but when the network is down it does not give any meaningful messages.
|
||
|
// Network failures log the user out in _getCurrentUserAsync() when it uses Auth0.
|
||
|
const errData = e.message;
|
||
|
switch (errData.error) {
|
||
|
case 'invalid_user_password':
|
||
|
return new (_XDLError || _load_XDLError()).default((_ErrorCode || _load_ErrorCode()).default.INVALID_USERNAME_PASSWORD, 'Invalid username or password');
|
||
|
case 'too_many_attempts':
|
||
|
return new (_XDLError || _load_XDLError()).default((_ErrorCode || _load_ErrorCode()).default.TOO_MANY_ATTEMPTS, errData.error_description);
|
||
|
default:
|
||
|
return new Error(errData.error_description);
|
||
|
}
|
||
|
return e;
|
||
|
}
|
||
|
|
||
|
function _buildAuth0SocialLoginUrl(auth0Options, loginOptions) {
|
||
|
const qsData = {
|
||
|
scope: 'openid offline_access username nickname',
|
||
|
response_type: loginOptions.responseType,
|
||
|
response_mode: loginOptions.responseMode,
|
||
|
connection: loginOptions.connection,
|
||
|
device: 'xdl',
|
||
|
client_id: auth0Options.clientID,
|
||
|
redirect_uri: auth0Options.callbackURL
|
||
|
};
|
||
|
|
||
|
const queryString = _querystring.default.stringify(qsData);
|
||
|
|
||
|
return `https://${AUTH0_DOMAIN}/authorize?${queryString}`;
|
||
|
}
|
||
|
|
||
|
function _auth0JSInstanceWithOptions(options = {}) {
|
||
|
const Auth0 = require('auth0-js');
|
||
|
|
||
|
let auth0Options = _extends({
|
||
|
domain: AUTH0_DOMAIN,
|
||
|
responseType: 'token'
|
||
|
}, options);
|
||
|
|
||
|
const Auth0Instance = (_bluebird || _load_bluebird()).default.promisifyAll(new Auth0(auth0Options));
|
||
|
|
||
|
return Auth0Instance;
|
||
|
}
|
||
|
|
||
|
function _nodeAuth0InstanceWithOptions(options = {}) {
|
||
|
let auth0Options = _extends({
|
||
|
domain: AUTH0_DOMAIN,
|
||
|
clientId: options.clientID || options.clientId
|
||
|
}, options);
|
||
|
|
||
|
let Auth0Instance;
|
||
|
if (auth0Options.management === true) {
|
||
|
auth0Options = (_lodash || _load_lodash()).default.omit(auth0Options, 'management');
|
||
|
const ManagementClient = require('auth0').ManagementClient;
|
||
|
Auth0Instance = new ManagementClient(auth0Options);
|
||
|
} else {
|
||
|
const AuthenticationClient = require('auth0').AuthenticationClient;
|
||
|
Auth0Instance = new AuthenticationClient(auth0Options);
|
||
|
}
|
||
|
|
||
|
return Auth0Instance;
|
||
|
}
|
||
|
|
||
|
function _parseAuth0Profile(rawProfile) {
|
||
|
if (!rawProfile || typeof rawProfile !== 'object') {
|
||
|
return rawProfile;
|
||
|
}
|
||
|
return Object.keys(rawProfile).reduce((p, key) => {
|
||
|
p[(_lodash || _load_lodash()).default.camelCase(key)] = _parseAuth0Profile(rawProfile[key]);
|
||
|
return p;
|
||
|
}, {});
|
||
|
}
|
||
|
|
||
|
function _prepareAuth0Profile(niceProfile) {
|
||
|
if (typeof niceProfile !== 'object') {
|
||
|
return niceProfile;
|
||
|
}
|
||
|
|
||
|
return Object.keys(niceProfile).reduce((p, key) => {
|
||
|
p[(_lodash || _load_lodash()).default.snakeCase(key)] = _prepareAuth0Profile(niceProfile[key]);
|
||
|
return p;
|
||
|
}, {});
|
||
|
}
|
||
|
|
||
|
class Deferred {
|
||
|
|
||
|
constructor() {
|
||
|
this.promise = new Promise((resolve, reject) => {
|
||
|
this.reject = reject;
|
||
|
this.resolve = resolve;
|
||
|
});
|
||
|
}
|
||
|
}
|
||
|
//# sourceMappingURL=__sourcemaps__/User.js.map
|