1import assert from 'assert'; 2import chalk from 'chalk'; 3 4import * as Log from '../../log'; 5import { env } from '../../utils/env'; 6import { CommandError } from '../../utils/errors'; 7import { learnMore } from '../../utils/link'; 8import promptAsync, { Question } from '../../utils/prompts'; 9import { ApiV2Error } from '../rest/client'; 10import { retryUsernamePasswordAuthWithOTPAsync } from './otp'; 11import { Actor, getUserAsync, loginAsync } from './user'; 12 13/** Show login prompt while prompting for missing credentials. */ 14export async function showLoginPromptAsync({ 15 printNewLine = false, 16 otp, 17 ...options 18}: { 19 printNewLine?: boolean; 20 username?: string; 21 password?: string; 22 otp?: string; 23} = {}): Promise<void> { 24 if (env.EXPO_OFFLINE) { 25 throw new CommandError('OFFLINE', 'Cannot authenticate in offline-mode'); 26 } 27 const hasCredentials = options.username && options.password; 28 29 if (printNewLine) { 30 Log.log(); 31 } 32 33 Log.log(hasCredentials ? 'Logging in to EAS' : 'Log in to EAS'); 34 35 let username = options.username; 36 let password = options.password; 37 38 if (!hasCredentials) { 39 const resolved = await promptAsync( 40 [ 41 !options.username && { 42 type: 'text', 43 name: 'username', 44 message: 'Email or username', 45 }, 46 !options.password && { 47 type: 'password', 48 name: 'password', 49 message: 'Password', 50 }, 51 ].filter(Boolean) as Question<string>[], 52 { 53 nonInteractiveHelp: `Use the EXPO_TOKEN environment variable to authenticate in CI (${learnMore( 54 'https://docs.expo.dev/accounts/programmatic-access/' 55 )})`, 56 } 57 ); 58 username ??= resolved.username; 59 password ??= resolved.password; 60 } 61 // This is just for the types. 62 assert(username && password); 63 64 try { 65 await loginAsync({ 66 username, 67 password, 68 otp, 69 }); 70 } catch (e) { 71 if (e instanceof ApiV2Error && e.expoApiV2ErrorCode === 'ONE_TIME_PASSWORD_REQUIRED') { 72 await retryUsernamePasswordAuthWithOTPAsync( 73 username, 74 password, 75 e.expoApiV2ErrorMetadata as any 76 ); 77 } else { 78 throw e; 79 } 80 } 81} 82 83/** Ensure the user is logged in, if not, prompt to login. */ 84export async function ensureLoggedInAsync(): Promise<Actor> { 85 let user = await getUserAsync().catch(() => null); 86 87 if (!user) { 88 Log.warn(chalk.yellow`An Expo user account is required to proceed.`); 89 await showLoginPromptAsync({ printNewLine: true }); 90 user = await getUserAsync(); 91 } 92 93 assert(user, 'User should be logged in'); 94 return user; 95} 96