2023-06-23 13:36:23 -04:00
use log ::* ;
2023-06-25 13:11:24 -04:00
use phonenumber ::PhoneNumber ;
2023-06-23 13:36:23 -04:00
use secrecy ::ExposeSecret ;
use steamguard ::{
2023-06-25 13:11:24 -04:00
accountlinker ::AccountLinkSuccess , phonelinker ::PhoneLinker , steamapi ::PhoneClient ,
2023-07-02 08:57:13 -04:00
token ::Tokens , AccountLinkError , AccountLinker , FinalizeLinkError ,
2023-06-23 13:36:23 -04:00
} ;
use crate ::{ tui , AccountManager } ;
use super ::* ;
#[ derive(Debug, Clone, Parser) ]
#[ clap(about = " Set up a new account with steamguard-cli " ) ]
pub struct SetupCommand ;
2023-07-02 08:57:13 -04:00
impl < T > ManifestCommand < T > for SetupCommand
where
T : Transport + Clone ,
{
fn execute ( & self , transport : T , manager : & mut AccountManager ) -> anyhow ::Result < ( ) > {
2023-06-23 13:36:23 -04:00
eprintln! ( " Log in to the account that you want to link to steamguard-cli " ) ;
eprint! ( " Username: " ) ;
let username = tui ::prompt ( ) . to_lowercase ( ) ;
let account_name = username . clone ( ) ;
if manager . account_exists ( & username ) {
bail! (
" Account {} already exists in manifest, remove it first " ,
username
) ;
}
info! ( " Logging in to {} " , username ) ;
2023-07-02 08:57:13 -04:00
let tokens = crate ::do_login_raw ( transport . clone ( ) , username )
. expect ( " Failed to log in. Account has not been linked. " ) ;
2023-06-23 13:36:23 -04:00
info! ( " Adding authenticator... " ) ;
2023-07-02 08:57:13 -04:00
let mut linker = AccountLinker ::new ( transport . clone ( ) , tokens ) ;
2023-06-23 13:36:23 -04:00
let link : AccountLinkSuccess ;
loop {
match linker . link ( ) {
Ok ( a ) = > {
link = a ;
break ;
}
Err ( AccountLinkError ::MustProvidePhoneNumber ) = > {
2023-06-25 13:11:24 -04:00
eprintln! ( " Looks like you don't have a phone number on this account. " ) ;
2023-07-02 08:57:13 -04:00
do_add_phone_number ( transport . clone ( ) , linker . tokens ( ) ) ? ;
2023-06-23 13:36:23 -04:00
}
Err ( AccountLinkError ::MustConfirmEmail ) = > {
println! ( " Check your email and click the link. " ) ;
tui ::pause ( ) ;
}
Err ( err ) = > {
error! (
" Failed to link authenticator. Account has not been linked. {} " ,
err
) ;
return Err ( err . into ( ) ) ;
}
}
}
let mut server_time = link . server_time ( ) ;
let phone_number_hint = link . phone_number_hint ( ) . to_owned ( ) ;
manager . add_account ( link . into_account ( ) ) ;
match manager . save ( ) {
Ok ( _ ) = > { }
Err ( err ) = > {
error! ( " Aborting the account linking process because we failed to save the manifest. This is really bad. Here is the error: {} " , err ) ;
2023-06-25 13:11:24 -04:00
eprintln! (
2023-06-23 13:36:23 -04:00
" Just in case, here is the account info. Save it somewhere just in case! \n {:#?} " ,
manager . get_account ( & account_name ) . unwrap ( ) . lock ( ) . unwrap ( )
) ;
return Err ( err ) ;
}
}
let account_arc = manager
. get_account ( & account_name )
. expect ( " account was not present in manifest " ) ;
let mut account = account_arc . lock ( ) . unwrap ( ) ;
2023-06-25 13:11:24 -04:00
eprintln! ( " Authenticator has not yet been linked. Before continuing with finalization, please take the time to write down your revocation code: {} " , account . revocation_code . expose_secret ( ) ) ;
2023-06-23 13:36:23 -04:00
tui ::pause ( ) ;
debug! ( " attempting link finalization " ) ;
println! (
" A code has been sent to your phone number ending in {}. " ,
phone_number_hint
) ;
print! ( " Enter SMS code: " ) ;
let sms_code = tui ::prompt ( ) ;
let mut tries = 0 ;
loop {
match linker . finalize ( server_time , & mut account , sms_code . clone ( ) ) {
Ok ( _ ) = > break ,
Err ( FinalizeLinkError ::WantMore { server_time : s } ) = > {
server_time = s ;
debug! ( " steam wants more 2fa codes (tries: {}) " , tries ) ;
tries + = 1 ;
if tries > = 30 {
error! ( " Failed to finalize: unable to generate valid 2fa codes " ) ;
bail! ( " Failed to finalize: unable to generate valid 2fa codes " ) ;
}
}
Err ( err ) = > {
error! ( " Failed to finalize: {} " , err ) ;
return Err ( err . into ( ) ) ;
}
}
}
let revocation_code = account . revocation_code . clone ( ) ;
drop ( account ) ; // explicitly drop the lock so we don't hang on the mutex
2023-06-25 13:11:24 -04:00
info! ( " Authenticator finalized. " ) ;
2023-06-23 13:36:23 -04:00
match manager . save ( ) {
Ok ( _ ) = > { }
Err ( err ) = > {
2023-06-25 13:11:24 -04:00
error! (
2023-06-23 13:36:23 -04:00
" Failed to save manifest, but we were able to save it before. {} " ,
err
) ;
return Err ( err ) ;
}
}
2023-06-25 13:11:24 -04:00
eprintln! (
2023-06-23 13:36:23 -04:00
" Authenticator has been finalized. Please actually write down your revocation code: {} " ,
revocation_code . expose_secret ( )
) ;
Ok ( ( ) )
}
}
2023-06-25 13:11:24 -04:00
2023-07-02 08:57:13 -04:00
pub fn do_add_phone_number < T : Transport > ( transport : T , tokens : & Tokens ) -> anyhow ::Result < ( ) > {
let client = PhoneClient ::new ( transport ) ;
2023-06-25 13:11:24 -04:00
let linker = PhoneLinker ::new ( client , tokens . clone ( ) ) ;
let phone_number : PhoneNumber ;
loop {
eprintln! ( " Enter your phone number, including country code, in this format: +1 1234567890 " ) ;
eprint! ( " Phone number: " ) ;
let number = tui ::prompt ( ) ;
match phonenumber ::parse ( None , & number ) {
Ok ( p ) = > {
phone_number = p ;
break ;
}
Err ( err ) = > {
error! ( " Failed to parse phone number: {} " , err ) ;
}
}
}
let resp = linker . set_account_phone_number ( phone_number ) ? ;
eprintln! (
" Please click the link in the email sent to {} " ,
resp . confirmation_email_address ( )
) ;
tui ::pause ( ) ;
debug! ( " sending phone verification code " ) ;
linker . send_phone_verification_code ( 0 ) ? ;
loop {
eprint! ( " Enter the code sent to your phone: " ) ;
let code = tui ::prompt ( ) ;
match linker . verify_account_phone_with_code ( code ) {
Ok ( _ ) = > break ,
Err ( err ) = > {
error! ( " Failed to verify phone number: {} " , err ) ;
}
}
}
info! ( " Successfully added phone number to account " ) ;
Ok ( ( ) )
}