- allow extracting the http client from web api transport - refactor most commands so that they can accept an external transport - update confirmer to use transport's http client - update remove command - update TwoFactorClient so it doesn't need to be mutable - update get_server_time so it requires a transport - update remove_authenticator - update login proceedure to use given transport - update usages of do_login - update setup - update trade - update code command - update qr-login command - make borrowcheck happy - make WebApiTransport Clone and remove Default impl - remove dead code - fix lints closes #177
182 lines
5 KiB
Rust
182 lines
5 KiB
Rust
use log::*;
|
|
use phonenumber::PhoneNumber;
|
|
use secrecy::ExposeSecret;
|
|
use steamguard::{
|
|
accountlinker::AccountLinkSuccess, phonelinker::PhoneLinker, steamapi::PhoneClient,
|
|
token::Tokens, AccountLinkError, AccountLinker, FinalizeLinkError,
|
|
};
|
|
|
|
use crate::{tui, AccountManager};
|
|
|
|
use super::*;
|
|
|
|
#[derive(Debug, Clone, Parser)]
|
|
#[clap(about = "Set up a new account with steamguard-cli")]
|
|
pub struct SetupCommand;
|
|
|
|
impl<T> ManifestCommand<T> for SetupCommand
|
|
where
|
|
T: Transport + Clone,
|
|
{
|
|
fn execute(&self, transport: T, manager: &mut AccountManager) -> anyhow::Result<()> {
|
|
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);
|
|
let tokens = crate::do_login_raw(transport.clone(), username)
|
|
.expect("Failed to log in. Account has not been linked.");
|
|
|
|
info!("Adding authenticator...");
|
|
let mut linker = AccountLinker::new(transport.clone(), tokens);
|
|
let link: AccountLinkSuccess;
|
|
loop {
|
|
match linker.link() {
|
|
Ok(a) => {
|
|
link = a;
|
|
break;
|
|
}
|
|
Err(AccountLinkError::MustProvidePhoneNumber) => {
|
|
eprintln!("Looks like you don't have a phone number on this account.");
|
|
do_add_phone_number(transport.clone(), linker.tokens())?;
|
|
}
|
|
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);
|
|
eprintln!(
|
|
"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();
|
|
|
|
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());
|
|
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
|
|
|
|
info!("Authenticator finalized.");
|
|
match manager.save() {
|
|
Ok(_) => {}
|
|
Err(err) => {
|
|
error!(
|
|
"Failed to save manifest, but we were able to save it before. {}",
|
|
err
|
|
);
|
|
return Err(err);
|
|
}
|
|
}
|
|
|
|
eprintln!(
|
|
"Authenticator has been finalized. Please actually write down your revocation code: {}",
|
|
revocation_code.expose_secret()
|
|
);
|
|
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
pub fn do_add_phone_number<T: Transport>(transport: T, tokens: &Tokens) -> anyhow::Result<()> {
|
|
let client = PhoneClient::new(transport);
|
|
|
|
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(())
|
|
}
|