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