"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.UserSecondFactorDeviceMethod = exports.REGISTRATION_URL = void 0; exports._retryUsernamePasswordAuthWithOTPAsync = _retryUsernamePasswordAuthWithOTPAsync; exports.login = login; exports.loginOrRegisterAsync = loginOrRegisterAsync; exports.loginOrRegisterIfLoggedOutAsync = loginOrRegisterIfLoggedOutAsync; exports.openRegistrationInBrowser = openRegistrationInBrowser; function _assert() { const data = _interopRequireDefault(require("assert")); _assert = function () { return data; }; return data; } function _betterOpn() { const data = _interopRequireDefault(require("better-opn")); _betterOpn = function () { return data; }; return data; } function _chalk() { const data = _interopRequireDefault(require("chalk")); _chalk = function () { return data; }; return data; } function _commander() { const data = _interopRequireDefault(require("commander")); _commander = function () { return data; }; return data; } function _xdl() { const data = require("xdl"); _xdl = function () { return data; }; return data; } function _ApiV() { const data = require("xdl/build/ApiV2"); _ApiV = function () { return data; }; return data; } function _CommandError() { const data = _interopRequireWildcard(require("../../CommandError")); _CommandError = function () { return data; }; return data; } function _log() { const data = _interopRequireDefault(require("../../log")); _log = function () { return data; }; return data; } function _ora() { const data = require("../../utils/ora"); _ora = function () { return data; }; return data; } function _prompts() { const data = _interopRequireWildcard(require("../../utils/prompts")); _prompts = function () { return data; }; return data; } function _validators() { const data = require("../../utils/validators"); _validators = function () { return data; }; return data; } function _getRequireWildcardCache(nodeInterop) { if (typeof WeakMap !== "function") return null; var cacheBabelInterop = new WeakMap(); var cacheNodeInterop = new WeakMap(); return (_getRequireWildcardCache = function (nodeInterop) { return nodeInterop ? cacheNodeInterop : cacheBabelInterop; })(nodeInterop); } function _interopRequireWildcard(obj, nodeInterop) { if (!nodeInterop && obj && obj.__esModule) { return obj; } if (obj === null || typeof obj !== "object" && typeof obj !== "function") { return { default: obj }; } var cache = _getRequireWildcardCache(nodeInterop); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (key !== "default" && Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj.default = obj; if (cache) { cache.set(obj, newObj); } return newObj; } function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } _xdl().UserManager.initialize(); let UserSecondFactorDeviceMethod; exports.UserSecondFactorDeviceMethod = UserSecondFactorDeviceMethod; (function (UserSecondFactorDeviceMethod) { UserSecondFactorDeviceMethod["AUTHENTICATOR"] = "authenticator"; UserSecondFactorDeviceMethod["SMS"] = "sms"; })(UserSecondFactorDeviceMethod || (exports.UserSecondFactorDeviceMethod = UserSecondFactorDeviceMethod = {})); async function loginOrRegisterAsync() { _log().default.warn('An Expo user account is required to proceed.'); // Always try to auto-login when these variables are set, even in non-interactive mode if (process.env.EXPO_CLI_USERNAME && process.env.EXPO_CLI_PASSWORD) { return login({ username: process.env.EXPO_CLI_USERNAME, password: process.env.EXPO_CLI_PASSWORD }); } if (_commander().default.nonInteractive) { throw new (_CommandError().default)('NOT_LOGGED_IN', `Not logged in. Use \`${_commander().default.name()} login -u username -p password\` to log in.`); } const question = { type: 'select', name: 'action', message: 'How would you like to authenticate?', choices: [{ title: 'Make a new Expo account', value: 'register' }, { title: 'Log in with an existing Expo account', value: 'existingUser' }, { title: 'Cancel', value: 'cancel' }] }; const { action } = await (0, _prompts().default)(question); if (action === 'register') { openRegistrationInBrowser(); _log().default.newLine(); _log().default.log(`Log in with ${_chalk().default.bold('expo login')} after you have created your account through the website.`); throw new (_CommandError().SilentError)(); } else if (action === 'existingUser') { return login({}); } else { throw new (_CommandError().default)('BAD_CHOICE', 'Not logged in.'); } } async function loginOrRegisterIfLoggedOutAsync() { const user = await _xdl().UserManager.getCurrentUserOnlyAsync(); if (user) { return user; } return await loginOrRegisterAsync(); } async function login(options) { const user = await _xdl().UserManager.getCurrentUserAsync({ silent: true }); if (user !== null && user !== void 0 && user.accessToken) { throw new (_CommandError().default)('ACCESS_TOKEN_ERROR', 'Please remove the EXPO_TOKEN environment var to login with a different user.'); } const nonInteractive = options.parent && options.parent.nonInteractive; if (!nonInteractive) { if (user) { const action = await (0, _prompts().confirmAsync)({ message: `You are already logged in as ${_chalk().default.green(user.username)}. Log in as new user?` }); if (!action) { // If user chooses to stay logged in, return return user; } } return _usernamePasswordAuth(options.username, options.password, options.otp); } else if (options.username && options.password) { return _usernamePasswordAuth(options.username, options.password, options.otp); } else if (options.username && process.env.EXPO_CLI_PASSWORD) { return _usernamePasswordAuth(options.username, process.env.EXPO_CLI_PASSWORD, options.otp); } else { throw new (_CommandError().default)('NON_INTERACTIVE', "Username and password not provided in non-interactive mode. Set the EXPO_CLI_PASSWORD environment variable if you don't want to pass in passwords through the command line."); } } /** * Prompt for an OTP with the option to cancel the question by answering empty (pressing return key). */ async function _promptForOTPAsync(cancelBehavior) { const enterMessage = cancelBehavior === 'cancel' ? `press ${_chalk().default.bold('Enter')} to cancel` : `press ${_chalk().default.bold('Enter')} for more options`; const otpQuestion = { type: 'text', name: 'otp', message: `One-time password or backup code (${enterMessage}):` }; const { otp } = await (0, _prompts().default)(otpQuestion); if (!otp) { return null; } return otp; } /** * Prompt for user to choose a backup OTP method. If selected method is SMS, a request * for a new OTP will be sent to that method. Then, prompt for the OTP, and retry the user login. */ async function _promptForBackupOTPAsync(username, password, secondFactorDevices) { const nonPrimarySecondFactorDevices = secondFactorDevices.filter(device => !device.is_primary); if (nonPrimarySecondFactorDevices.length === 0) { throw new (_CommandError().default)('LOGIN_CANCELLED', 'No other second-factor devices set up. Ensure you have set up and certified a backup device.'); } const hasAuthenticatorSecondFactorDevice = nonPrimarySecondFactorDevices.find(device => device.method === UserSecondFactorDeviceMethod.AUTHENTICATOR); const smsNonPrimarySecondFactorDevices = nonPrimarySecondFactorDevices.filter(device => device.method === UserSecondFactorDeviceMethod.SMS); const authenticatorChoiceSentinel = -1; const cancelChoiceSentinel = -2; const deviceChoices = smsNonPrimarySecondFactorDevices.map((device, idx) => ({ title: device.sms_phone_number, value: idx })); if (hasAuthenticatorSecondFactorDevice) { deviceChoices.push({ title: 'Authenticator', value: authenticatorChoiceSentinel }); } deviceChoices.push({ title: 'Cancel', value: cancelChoiceSentinel }); const question = { message: 'Select a second-factor device:', choices: deviceChoices }; const selectedValue = await (0, _prompts().selectAsync)(question); if (selectedValue === cancelChoiceSentinel) { return null; } else if (selectedValue === authenticatorChoiceSentinel) { return await _promptForOTPAsync('cancel'); } const device = smsNonPrimarySecondFactorDevices[selectedValue]; const apiAnonymous = _xdl().ApiV2.clientForUser(); await apiAnonymous.postAsync('auth/send-sms-otp', { username, password, secondFactorDeviceID: device.id }); return await _promptForOTPAsync('cancel'); } /** * Handle the special case error indicating that a second-factor is required for * authentication. * * There are three cases we need to handle: * 1. User's primary second-factor device was SMS, OTP was automatically sent by the server to that * device already. In this case we should just prompt for the SMS OTP (or backup code), which the * user should be receiving shortly. We should give the user a way to cancel and the prompt and move * to case 3 below. * 2. User's primary second-factor device is authenticator. In this case we should prompt for authenticator * OTP (or backup code) and also give the user a way to cancel and move to case 3 below. * 3. User doesn't have a primary device or doesn't have access to their primary device. In this case * we should show a picker of the SMS devices that they can have an OTP code sent to, and when * the user picks one we show a prompt() for the sent OTP. */ async function _retryUsernamePasswordAuthWithOTPAsync(username, password, metadata) { const { secondFactorDevices, smsAutomaticallySent } = metadata; (0, _assert().default)(secondFactorDevices !== undefined && smsAutomaticallySent !== undefined, `Malformed OTP error metadata: ${metadata}`); const primaryDevice = secondFactorDevices.find(device => device.is_primary); let otp = null; if (smsAutomaticallySent) { (0, _assert().default)(primaryDevice, 'OTP should only automatically be sent when there is a primary device'); _log().default.nested(`One-time password was sent to the phone number ending in ${primaryDevice.sms_phone_number}.`); otp = await _promptForOTPAsync('menu'); } if ((primaryDevice === null || primaryDevice === void 0 ? void 0 : primaryDevice.method) === UserSecondFactorDeviceMethod.AUTHENTICATOR) { _log().default.nested('One-time password from authenticator required.'); otp = await _promptForOTPAsync('menu'); } // user bailed on case 1 or 2, wants to move to case 3 if (!otp) { otp = await _promptForBackupOTPAsync(username, password, secondFactorDevices); } if (!otp) { throw new (_CommandError().default)('LOGIN_CANCELLED', 'Cancelled login'); } return await _xdl().UserManager.loginAsync('user-pass', { username, password, otp }); } async function _usernamePasswordAuth(username, password, otp) { const questions = []; if (!username) { questions.push({ type: 'text', name: 'username', message: 'Username/Email Address:', format: val => val.trim(), validate: _validators().nonEmptyInput }); } if (!password) { questions.push({ type: 'password', name: 'password', message: 'Password:', format: val => val.trim(), validate: _validators().nonEmptyInput }); } const answers = await (0, _prompts().default)(questions); const data = { username: username || answers.username, password: password || answers.password, otp: otp || answers.otp }; let user; try { user = await _xdl().UserManager.loginAsync('user-pass', data); } catch (e) { if (e instanceof _ApiV().ApiV2Error && e.code === 'ONE_TIME_PASSWORD_REQUIRED') { user = await _retryUsernamePasswordAuthWithOTPAsync(data.username, data.password, e.metadata); } else { throw e; } } if (user) { _log().default.log(`\nSuccess. You are now logged in as ${_chalk().default.green(user.username)}.`); return user; } else { throw new Error('Unexpected Error: No user returned from the API'); } } const REGISTRATION_URL = `https://expo.dev/signup`; exports.REGISTRATION_URL = REGISTRATION_URL; async function openRegistrationInBrowser() { const spinner = (0, _ora().ora)(`Opening ${REGISTRATION_URL}...`).start(); const opened = await (0, _betterOpn().default)(REGISTRATION_URL); if (opened) { spinner.succeed(`Opened ${REGISTRATION_URL} in your web browser.`); } else { spinner.fail(`Unable to open a web browser. Please open your browser and navigate to ${REGISTRATION_URL}.`); } } //# sourceMappingURL=accounts.js.map