You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

342 lines
13 KiB

"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