UNPKG

@expo/xdl

Version:
569 lines (434 loc) 14.5 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.default = exports.UserManagerInstance = exports.ANONYMOUS_USERNAME = void 0; function _camelCase() { const data = _interopRequireDefault(require("lodash/camelCase")); _camelCase = function () { return data; }; return data; } function _isEmpty() { const data = _interopRequireDefault(require("lodash/isEmpty")); _isEmpty = function () { return data; }; return data; } function _snakeCase() { const data = _interopRequireDefault(require("lodash/snakeCase")); _snakeCase = function () { return data; }; return data; } function _Analytics() { const data = _interopRequireDefault(require("./Analytics")); _Analytics = function () { return data; }; return data; } function _ApiV() { const data = _interopRequireDefault(require("./ApiV2")); _ApiV = function () { return data; }; return data; } function _Config() { const data = _interopRequireDefault(require("./Config")); _Config = function () { return data; }; return data; } function _Logger() { const data = _interopRequireDefault(require("./Logger")); _Logger = function () { return data; }; return data; } function _UserSettings() { const data = _interopRequireDefault(require("./UserSettings")); _UserSettings = function () { return data; }; return data; } function _Utils() { const data = require("./Utils"); _Utils = function () { return data; }; return data; } function _XDLError() { const data = _interopRequireDefault(require("./XDLError")); _XDLError = function () { return data; }; return data; } function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; } const ANONYMOUS_USERNAME = 'anonymous'; exports.ANONYMOUS_USERNAME = ANONYMOUS_USERNAME; class UserManagerInstance { constructor() { _defineProperty(this, "_currentUser", null); _defineProperty(this, "_getSessionLock", new (_Utils().Semaphore)()); _defineProperty(this, "_interactiveAuthenticationCallbackAsync", void 0); } static getGlobalInstance() { if (!__globalInstance) { __globalInstance = new UserManagerInstance(); } return __globalInstance; } initialize() { this._currentUser = null; this._getSessionLock = new (_Utils().Semaphore)(); } /** * Logs in a user for a given login type. * * Valid login types are: * - "user-pass": Username and password authentication * * If the login type is "user-pass", we directly make the request to www * to login a user. */ async loginAsync(loginType, loginArgs) { if (loginType === 'user-pass') { if (!loginArgs) { throw new Error(`The 'user-pass' login type requires a username and password.`); } const apiAnonymous = _ApiV().default.clientForUser(); const loginResp = await apiAnonymous.postAsync('auth/loginAsync', { username: loginArgs.username, password: loginArgs.password, otp: loginArgs.otp }); if (loginResp.error) { throw new (_XDLError().default)('INVALID_USERNAME_PASSWORD', loginResp['error_description']); } const user = await this._getProfileAsync({ currentConnection: 'Username-Password-Authentication', sessionSecret: loginResp.sessionSecret }); return user; } else { throw new Error(`Invalid login type provided. Must be 'user-pass'.`); } } async registerAsync(userData, user = null) { let actor = user; if (!actor) { actor = await this.getCurrentUserAsync(); } if (actor) { await this.logoutAsync(); actor = null; } try { // Create or update the profile let registeredUser = await this.createOrUpdateUserAsync({ connection: 'Username-Password-Authentication', // Always create/update username password email: userData.email, givenName: userData.givenName, familyName: userData.familyName, username: userData.username, password: userData.password }); registeredUser = await this.loginAsync('user-pass', { username: userData.username, password: userData.password }); return registeredUser; } catch (e) { console.error(e); throw new (_XDLError().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. */ async ensureLoggedInAsync() { if (_Config().default.offline) { throw new (_XDLError().default)('NETWORK_REQUIRED', "Can't verify user without network access"); } let user = await this.getCurrentUserAsync({ silent: true }); if (!user && this._interactiveAuthenticationCallbackAsync) { user = await this._interactiveAuthenticationCallbackAsync(); } if (!user) { throw new (_XDLError().default)('NOT_LOGGED_IN', 'Not logged in'); } return user; } setInteractiveAuthenticationCallback(callback) { this._interactiveAuthenticationCallbackAsync = callback; } async _readUserData() { let auth = await _UserSettings().default.getAsync('auth', null); if ((0, _isEmpty().default)(auth)) { // XXX(ville): // We sometimes read an empty string from ~/.expo/state.json, // even though it has valid credentials in it. // We don't know why. // An empty string can't be parsed as JSON, so an empty default object is returned. // In this case, retrying usually helps. auth = await _UserSettings().default.getAsync('auth', null); } if (typeof auth === 'undefined') { return null; } return auth; } /** * Get the current user based on the available token. * If there is no current token, returns null. */ async getCurrentUserAsync(options) { await this._getSessionLock.acquire(); try { const currentUser = this._currentUser; // If user is cached and there is an accessToken or sessionSecret, return the user if (currentUser && (currentUser.accessToken || currentUser.sessionSecret)) { return currentUser; } if (_Config().default.offline) { return null; } const data = await this._readUserData(); const accessToken = _UserSettings().default.accessToken(); // No token, no session, no current user. Need to login if (!accessToken && !(data === null || data === void 0 ? void 0 : data.sessionSecret)) { return null; } try { if (accessToken) { return await this._getProfileAsync({ accessToken, currentConnection: 'Access-Token-Authentication' }); } return await this._getProfileAsync({ currentConnection: data === null || data === void 0 ? void 0 : data.currentConnection, sessionSecret: data === null || data === void 0 ? void 0 : data.sessionSecret }); } catch (e) { if (!(options && options.silent)) { _Logger().default.global.warn('Fetching the user profile failed'); _Logger().default.global.warn(e); } if (e.code === 'UNAUTHORIZED_ERROR') { return null; } throw e; } } finally { this._getSessionLock.release(); } } /** * Get the current user and check if it's a robot. * If the user is not a robot, it will throw an error. */ async getCurrentUserOnlyAsync() { const user = await this.getCurrentUserAsync(); if (user && user.kind !== 'user') { throw new (_XDLError().default)('ROBOT_ACCOUNT_ERROR', 'This action is not supported for robot users.'); } return user; } /** * Get the current user and check if it's a robot. * If the user is not a robot, it will throw an error. */ async getCurrentRobotUserOnlyAsync() { const user = await this.getCurrentUserAsync(); if (user && user.kind !== 'robot') { throw new (_XDLError().default)('USER_ACCOUNT_ERROR', 'This action is not supported for normal users.'); } return user; } async getCurrentUsernameAsync() { const token = _UserSettings().default.accessToken(); if (token) { const user = await this.getCurrentUserAsync(); if (user === null || user === void 0 ? void 0 : user.username) { return user.username; } } const data = await this._readUserData(); if (data === null || data === void 0 ? void 0 : data.username) { return data.username; } return null; } async getSessionAsync() { const token = _UserSettings().default.accessToken(); if (token) { return { accessToken: token }; } const data = await this._readUserData(); if (data === null || data === void 0 ? void 0 : data.sessionSecret) { return { sessionSecret: data.sessionSecret }; } return null; } /** * Create or update a user. */ async createOrUpdateUserAsync(userData) { var _currentUser; let currentUser = this._currentUser; if (!currentUser) { // attempt to get the current user currentUser = await this.getCurrentUserAsync(); } if (((_currentUser = currentUser) === null || _currentUser === void 0 ? void 0 : _currentUser.kind) === 'robot') { throw new (_XDLError().default)('ROBOT_ACCOUNT_ERROR', 'This action is not available for robot users'); } const api = _ApiV().default.clientForUser(currentUser); const { user: updatedUser } = await api.postAsync('auth/createOrUpdateUser', { userData: _prepareAuth0Profile(userData) }); this._currentUser = { ...this._currentUser, ..._parseAuth0Profile(updatedUser), kind: 'user' }; return this._currentUser; } /** * Logout */ async logoutAsync() { var _this$_currentUser, _this$_currentUser2; if (((_this$_currentUser = this._currentUser) === null || _this$_currentUser === void 0 ? void 0 : _this$_currentUser.kind) === 'robot') { throw new (_XDLError().default)('ROBOT_ACCOUNT_ERROR', 'This action is not available for robot users'); } // Only send logout events events for users without access tokens if (this._currentUser && !((_this$_currentUser2 = this._currentUser) === null || _this$_currentUser2 === void 0 ? void 0 : _this$_currentUser2.accessToken)) { _Analytics().default.logEvent('Logout', { userId: this._currentUser.userId, username: this._currentUser.username, currentConnection: this._currentUser.currentConnection }); } this._currentUser = null; // Delete saved auth info await _UserSettings().default.deleteKeyAsync('auth'); } /** * Forgot Password */ async forgotPasswordAsync(usernameOrEmail) { const apiAnonymous = _ApiV().default.clientForUser(); return apiAnonymous.postAsync('auth/forgotPasswordAsync', { 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 * * Also updates UserManager._currentUser. * * @private */ async _getProfileAsync({ currentConnection, sessionSecret, accessToken }) { let user; const api = _ApiV().default.clientForUser({ sessionSecret, accessToken }); user = await api.getAsync('auth/userInfo'); if (!user) { throw new Error('Unable to fetch user.'); } user = { ..._parseAuth0Profile(user), // We need to inherit the "robot" type only, the rest is considered "user" but returned as "person". kind: user.user_type === 'robot' ? 'robot' : 'user', currentConnection, sessionSecret, accessToken }; // Create a "username" to use in current terminal UI (e.g. expo whoami) if (user.kind === 'robot') { user.username = user.givenName ? `${user.givenName} (robot)` : 'robot'; } // note: do not persist the authorization token, must be env-var only if (!accessToken) { await _UserSettings().default.setAsync('auth', { userId: user.userId, username: user.username, currentConnection, sessionSecret }); } // If no currentUser, or currentUser.id differs from profiles // user id, that means we have a new login if ((!this._currentUser || this._currentUser.userId !== user.userId) && user.username && user.username !== '') { if (!accessToken) { // Only send login events for users without access tokens _Analytics().default.logEvent('Login', { userId: user.userId, currentConnection: user.currentConnection, username: user.username }); } _Analytics().default.setUserProperties(user.username, { userId: user.userId, currentConnection: user.currentConnection, username: user.username, userType: user.kind }); } this._currentUser = user; return user; } } exports.UserManagerInstance = UserManagerInstance; let __globalInstance; var _default = UserManagerInstance.getGlobalInstance(); /** Private Methods **/ exports.default = _default; function _parseAuth0Profile(rawProfile) { if (!rawProfile || typeof rawProfile !== 'object') { return rawProfile; } return Object.keys(rawProfile).reduce((p, key) => { p[(0, _camelCase().default)(key)] = _parseAuth0Profile(rawProfile[key]); return p; }, {}); } function _prepareAuth0Profile(niceProfile) { if (typeof niceProfile !== 'object') { return niceProfile; } return Object.keys(niceProfile).reduce((p, key) => { p[(0, _snakeCase().default)(key)] = _prepareAuth0Profile(niceProfile[key]); return p; }, {}); } //# sourceMappingURL=__sourcemaps__/User.js.map