2021-03-24 22:49:09 +01:00
|
|
|
extern crate rpassword;
|
2021-08-01 14:43:18 +02:00
|
|
|
use clap::{crate_version, App, Arg};
|
2021-03-27 15:35:52 +01:00
|
|
|
use log::*;
|
2021-08-01 14:43:18 +02:00
|
|
|
use std::{
|
2021-08-14 16:01:25 +02:00
|
|
|
io::{stdout, Write},
|
2021-08-08 18:54:46 +02:00
|
|
|
path::Path,
|
|
|
|
sync::{Arc, Mutex},
|
2021-08-01 14:43:18 +02:00
|
|
|
};
|
2021-08-08 18:34:06 +02:00
|
|
|
use steamguard::{
|
2021-08-14 16:01:25 +02:00
|
|
|
steamapi, AccountLinkError, AccountLinker, Confirmation, FinalizeLinkError, LoginError,
|
|
|
|
SteamGuardAccount, UserLogin,
|
2021-08-01 14:43:18 +02:00
|
|
|
};
|
2021-03-24 22:49:09 +01:00
|
|
|
|
2021-04-04 16:40:16 +02:00
|
|
|
#[macro_use]
|
|
|
|
extern crate lazy_static;
|
2021-08-01 17:20:57 +02:00
|
|
|
#[macro_use]
|
|
|
|
extern crate anyhow;
|
2021-08-13 00:06:18 +02:00
|
|
|
extern crate dirs;
|
2021-08-01 14:43:18 +02:00
|
|
|
mod accountmanager;
|
2021-08-14 16:01:25 +02:00
|
|
|
mod demos;
|
|
|
|
mod tui;
|
2021-04-04 16:40:16 +02:00
|
|
|
|
2021-03-22 02:21:29 +01:00
|
|
|
fn main() {
|
2021-08-08 18:54:46 +02:00
|
|
|
let matches = App::new("steamguard-cli")
|
2021-03-27 14:31:38 +01:00
|
|
|
.version(crate_version!())
|
|
|
|
.bin_name("steamguard")
|
|
|
|
.author("dyc3 (Carson McManus)")
|
|
|
|
.about("Generate Steam 2FA codes and confirm Steam trades from the command line.")
|
|
|
|
.arg(
|
|
|
|
Arg::with_name("username")
|
|
|
|
.long("username")
|
|
|
|
.short("u")
|
2021-08-10 04:25:39 +02:00
|
|
|
.takes_value(true)
|
2021-03-27 14:31:38 +01:00
|
|
|
.help("Select the account you want by steam username. By default, the first account in the manifest is selected.")
|
2021-08-12 01:58:18 +02:00
|
|
|
.conflicts_with("all")
|
2021-03-27 14:31:38 +01:00
|
|
|
)
|
|
|
|
.arg(
|
|
|
|
Arg::with_name("all")
|
|
|
|
.long("all")
|
|
|
|
.short("a")
|
2021-03-27 17:14:34 +01:00
|
|
|
.takes_value(false)
|
2021-03-27 14:31:38 +01:00
|
|
|
.help("Select all accounts in the manifest.")
|
2021-08-12 01:58:18 +02:00
|
|
|
.conflicts_with("username")
|
2021-03-27 14:31:38 +01:00
|
|
|
)
|
|
|
|
.arg(
|
|
|
|
Arg::with_name("mafiles-path")
|
|
|
|
.long("mafiles-path")
|
|
|
|
.short("m")
|
|
|
|
.default_value("~/maFiles")
|
2021-08-13 00:06:18 +02:00
|
|
|
.help("Specify which folder your maFiles are in. This should be a path to a folder that contains manifest.json.")
|
2021-03-27 14:31:38 +01:00
|
|
|
)
|
|
|
|
.arg(
|
|
|
|
Arg::with_name("passkey")
|
|
|
|
.long("passkey")
|
|
|
|
.short("p")
|
|
|
|
.help("Specify your encryption passkey.")
|
|
|
|
)
|
2021-03-27 15:35:52 +01:00
|
|
|
.arg(
|
|
|
|
Arg::with_name("verbosity")
|
|
|
|
.short("v")
|
|
|
|
.help("Log what is going on verbosely.")
|
|
|
|
.takes_value(false)
|
|
|
|
.multiple(true)
|
|
|
|
)
|
2021-03-27 14:31:38 +01:00
|
|
|
.subcommand(
|
|
|
|
App::new("trade")
|
|
|
|
.about("Interactive interface for trade confirmations")
|
|
|
|
.arg(
|
|
|
|
Arg::with_name("accept-all")
|
|
|
|
.short("a")
|
|
|
|
.long("accept-all")
|
2021-03-27 17:14:34 +01:00
|
|
|
.takes_value(false)
|
2021-03-27 14:31:38 +01:00
|
|
|
.help("Accept all open trade confirmations. Does not open interactive interface.")
|
|
|
|
)
|
|
|
|
)
|
2021-07-27 22:24:56 +02:00
|
|
|
.subcommand(
|
|
|
|
App::new("setup")
|
|
|
|
.about("Set up a new account with steamguard-cli")
|
|
|
|
)
|
2021-08-14 01:04:03 +02:00
|
|
|
.subcommand(
|
|
|
|
App::new("import")
|
|
|
|
.about("Import an account with steamguard already set up")
|
|
|
|
.arg(
|
|
|
|
Arg::with_name("files")
|
|
|
|
.required(true)
|
|
|
|
.multiple(true)
|
|
|
|
)
|
|
|
|
)
|
2021-08-12 01:39:29 +02:00
|
|
|
.subcommand(
|
|
|
|
App::new("remove")
|
|
|
|
.about("Remove the authenticator from an account.")
|
|
|
|
)
|
2021-07-30 01:42:45 +02:00
|
|
|
.subcommand(
|
|
|
|
App::new("debug")
|
|
|
|
.arg(
|
|
|
|
Arg::with_name("demo-conf-menu")
|
|
|
|
.help("Show an example confirmation menu using dummy data.")
|
|
|
|
.takes_value(false)
|
|
|
|
)
|
|
|
|
)
|
2021-03-27 14:31:38 +01:00
|
|
|
.get_matches();
|
|
|
|
|
2021-08-08 18:54:46 +02:00
|
|
|
let verbosity = matches.occurrences_of("verbosity") as usize + 2;
|
|
|
|
stderrlog::new()
|
|
|
|
.verbosity(verbosity)
|
|
|
|
.module(module_path!())
|
2021-08-10 01:48:18 +02:00
|
|
|
.module("steamguard")
|
2021-08-08 18:54:46 +02:00
|
|
|
.init()
|
|
|
|
.unwrap();
|
2021-03-22 02:21:29 +01:00
|
|
|
|
2021-08-08 18:54:46 +02:00
|
|
|
if let Some(demo_matches) = matches.subcommand_matches("debug") {
|
|
|
|
if demo_matches.is_present("demo-conf-menu") {
|
2021-08-14 16:01:25 +02:00
|
|
|
demos::demo_confirmation_menu();
|
2021-08-08 18:54:46 +02:00
|
|
|
}
|
|
|
|
return;
|
|
|
|
}
|
2021-07-30 01:42:45 +02:00
|
|
|
|
2021-08-13 00:36:03 +02:00
|
|
|
let mafiles_dir = if matches.occurrences_of("mafiles-path") > 0 {
|
|
|
|
matches.value_of("mafiles-path").unwrap().into()
|
|
|
|
} else {
|
|
|
|
get_mafiles_dir()
|
|
|
|
};
|
2021-08-13 00:06:18 +02:00
|
|
|
info!("reading manifest from {}", mafiles_dir);
|
|
|
|
let path = Path::new(&mafiles_dir).join("manifest.json");
|
2021-08-08 18:54:46 +02:00
|
|
|
let mut manifest: accountmanager::Manifest;
|
2021-08-13 00:06:18 +02:00
|
|
|
if !path.exists() {
|
|
|
|
error!("Did not find manifest in {}", mafiles_dir);
|
2021-08-14 17:10:21 +02:00
|
|
|
match tui::prompt_char(
|
|
|
|
format!("Would you like to create a manifest in {} ?", mafiles_dir).as_str(),
|
|
|
|
"Yn",
|
|
|
|
) {
|
|
|
|
'n' => {
|
2021-08-13 00:06:18 +02:00
|
|
|
info!("Aborting!");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
_ => {}
|
2021-08-08 18:54:46 +02:00
|
|
|
}
|
2021-08-13 00:06:18 +02:00
|
|
|
std::fs::create_dir_all(mafiles_dir).expect("failed to create directory");
|
|
|
|
|
|
|
|
manifest = accountmanager::Manifest::new(path.as_path());
|
2021-08-13 00:54:38 +02:00
|
|
|
} else {
|
2021-08-13 00:06:18 +02:00
|
|
|
match accountmanager::Manifest::load(path.as_path()) {
|
|
|
|
Ok(m) => {
|
|
|
|
manifest = m;
|
|
|
|
}
|
|
|
|
Err(e) => {
|
|
|
|
error!("Could not load manifest: {}", e);
|
|
|
|
return;
|
|
|
|
}
|
2021-08-08 18:54:46 +02:00
|
|
|
}
|
|
|
|
}
|
2021-03-27 15:35:52 +01:00
|
|
|
|
2021-08-09 00:32:50 +02:00
|
|
|
manifest
|
|
|
|
.load_accounts()
|
|
|
|
.expect("Failed to load accounts in manifest");
|
2021-07-27 22:24:56 +02:00
|
|
|
|
2021-08-08 18:54:46 +02:00
|
|
|
if matches.is_present("setup") {
|
2021-08-09 00:32:50 +02:00
|
|
|
println!("Log in to the account that you want to link to steamguard-cli");
|
|
|
|
let session = do_login_raw().expect("Failed to log in. Account has not been linked.");
|
|
|
|
|
|
|
|
let mut linker = AccountLinker::new(session);
|
|
|
|
let account: SteamGuardAccount;
|
|
|
|
loop {
|
|
|
|
match linker.link() {
|
|
|
|
Ok(a) => {
|
|
|
|
account = a;
|
|
|
|
break;
|
|
|
|
}
|
2021-08-10 01:09:48 +02:00
|
|
|
Err(AccountLinkError::MustRemovePhoneNumber) => {
|
|
|
|
println!("There is already a phone number on this account, please remove it and try again.");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
Err(AccountLinkError::MustProvidePhoneNumber) => {
|
2021-08-10 03:41:20 +02:00
|
|
|
println!("Enter your phone number in the following format: +1 123-456-7890");
|
|
|
|
print!("Phone number: ");
|
2021-08-14 16:01:25 +02:00
|
|
|
linker.phone_number = tui::prompt().replace(&['(', ')', '-'][..], "");
|
2021-08-10 01:09:48 +02:00
|
|
|
}
|
|
|
|
Err(AccountLinkError::AuthenticatorPresent) => {
|
|
|
|
println!("An authenticator is already present on this account.");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
Err(AccountLinkError::MustConfirmEmail) => {
|
|
|
|
println!("Check your email and click the link.");
|
2021-08-14 16:01:25 +02:00
|
|
|
tui::pause();
|
2021-08-10 01:09:48 +02:00
|
|
|
}
|
2021-08-09 00:32:50 +02:00
|
|
|
Err(err) => {
|
|
|
|
error!(
|
|
|
|
"Failed to link authenticator. Account has not been linked. {}",
|
|
|
|
err
|
|
|
|
);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
manifest.add_account(account);
|
|
|
|
match manifest.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);
|
|
|
|
println!(
|
|
|
|
"Just in case, here is the account info. Save it somewhere just in case!\n{:?}",
|
2021-08-09 06:09:34 +02:00
|
|
|
manifest.accounts.last().unwrap().lock().unwrap()
|
2021-08-09 00:32:50 +02:00
|
|
|
);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-08-09 06:09:34 +02:00
|
|
|
let mut account = manifest
|
|
|
|
.accounts
|
|
|
|
.last()
|
|
|
|
.as_ref()
|
|
|
|
.unwrap()
|
|
|
|
.clone()
|
|
|
|
.lock()
|
|
|
|
.unwrap();
|
|
|
|
|
2021-08-09 00:32:50 +02:00
|
|
|
debug!("attempting link finalization");
|
|
|
|
print!("Enter SMS code: ");
|
2021-08-14 16:01:25 +02:00
|
|
|
let sms_code = tui::prompt();
|
2021-08-09 06:09:34 +02:00
|
|
|
let mut tries = 0;
|
|
|
|
loop {
|
2021-08-10 00:44:42 +02:00
|
|
|
match linker.finalize(&mut account, sms_code.clone()) {
|
2021-08-09 06:09:34 +02:00
|
|
|
Ok(_) => break,
|
|
|
|
Err(FinalizeLinkError::WantMore) => {
|
|
|
|
debug!("steam wants more 2fa codes (tries: {})", tries);
|
|
|
|
tries += 1;
|
|
|
|
if tries >= 30 {
|
|
|
|
error!("Failed to finalize: unable to generate valid 2fa codes");
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
continue;
|
2021-08-10 00:44:42 +02:00
|
|
|
}
|
2021-08-09 06:09:34 +02:00
|
|
|
Err(err) => {
|
|
|
|
error!("Failed to finalize: {}", err);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2021-08-09 00:32:50 +02:00
|
|
|
|
2021-08-11 03:10:04 +02:00
|
|
|
println!("Authenticator finalized.");
|
|
|
|
match manifest.save() {
|
|
|
|
Ok(_) => {}
|
|
|
|
Err(err) => {
|
2021-08-11 04:45:17 +02:00
|
|
|
println!(
|
|
|
|
"Failed to save manifest, but we were able to save it before. {}",
|
|
|
|
err
|
|
|
|
);
|
2021-08-11 03:10:04 +02:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-08-14 01:04:03 +02:00
|
|
|
return;
|
|
|
|
} else if let Some(import_matches) = matches.subcommand_matches("import") {
|
|
|
|
for file_path in import_matches.values_of("files").unwrap() {
|
|
|
|
match manifest.import_account(file_path.into()) {
|
|
|
|
Ok(_) => {
|
|
|
|
info!("Imported account: {}", file_path);
|
|
|
|
}
|
|
|
|
Err(err) => {
|
|
|
|
error!("Failed to import account: {} {}", file_path, err);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
manifest.save().expect("Failed to save manifest.");
|
2021-08-08 18:54:46 +02:00
|
|
|
return;
|
|
|
|
}
|
2021-07-27 22:24:56 +02:00
|
|
|
|
2021-08-08 18:54:46 +02:00
|
|
|
let mut selected_accounts: Vec<Arc<Mutex<SteamGuardAccount>>> = vec![];
|
|
|
|
if matches.is_present("all") {
|
|
|
|
// manifest.accounts.iter().map(|a| selected_accounts.push(a.b));
|
|
|
|
for account in &manifest.accounts {
|
|
|
|
selected_accounts.push(account.clone());
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
for account in &manifest.accounts {
|
|
|
|
if !matches.is_present("username") {
|
|
|
|
selected_accounts.push(account.clone());
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
if matches.value_of("username").unwrap() == account.lock().unwrap().account_name {
|
|
|
|
selected_accounts.push(account.clone());
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2021-03-27 17:14:34 +01:00
|
|
|
|
2021-08-08 18:54:46 +02:00
|
|
|
debug!(
|
|
|
|
"selected accounts: {:?}",
|
|
|
|
selected_accounts
|
|
|
|
.iter()
|
|
|
|
.map(|a| a.lock().unwrap().account_name.clone())
|
|
|
|
.collect::<Vec<String>>()
|
|
|
|
);
|
2021-03-27 17:14:34 +01:00
|
|
|
|
2021-08-08 18:54:46 +02:00
|
|
|
if let Some(trade_matches) = matches.subcommand_matches("trade") {
|
|
|
|
info!("trade");
|
|
|
|
for a in selected_accounts.iter_mut() {
|
|
|
|
let mut account = a.lock().unwrap();
|
2021-04-04 20:07:06 +02:00
|
|
|
|
2021-08-08 18:54:46 +02:00
|
|
|
info!("Checking for trade confirmations");
|
|
|
|
let confirmations: Vec<Confirmation>;
|
|
|
|
loop {
|
|
|
|
match account.get_trade_confirmations() {
|
|
|
|
Ok(confs) => {
|
|
|
|
confirmations = confs;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
Err(_) => {
|
|
|
|
info!("failed to get trade confirmations, asking user to log in");
|
|
|
|
do_login(&mut account);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2021-07-29 15:08:06 +02:00
|
|
|
|
2021-08-08 18:54:46 +02:00
|
|
|
if trade_matches.is_present("accept-all") {
|
|
|
|
info!("accepting all confirmations");
|
|
|
|
for conf in &confirmations {
|
|
|
|
let result = account.accept_confirmation(conf);
|
|
|
|
debug!("accept confirmation result: {:?}", result);
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
if termion::is_tty(&stdout()) {
|
2021-08-14 16:01:25 +02:00
|
|
|
let (accept, deny) = tui::prompt_confirmation_menu(confirmations);
|
2021-08-08 18:54:46 +02:00
|
|
|
for conf in &accept {
|
|
|
|
let result = account.accept_confirmation(conf);
|
|
|
|
debug!("accept confirmation result: {:?}", result);
|
|
|
|
}
|
|
|
|
for conf in &deny {
|
|
|
|
let result = account.deny_confirmation(conf);
|
|
|
|
debug!("deny confirmation result: {:?}", result);
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
warn!("not a tty, not showing menu");
|
|
|
|
for conf in &confirmations {
|
|
|
|
println!("{}", conf.description());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2021-08-01 17:20:57 +02:00
|
|
|
|
2021-08-08 18:54:46 +02:00
|
|
|
manifest.save();
|
2021-08-12 01:39:29 +02:00
|
|
|
} else if let Some(_) = matches.subcommand_matches("remove") {
|
|
|
|
println!(
|
|
|
|
"This will remove the mobile authenticator from {} accounts: {}",
|
|
|
|
selected_accounts.len(),
|
|
|
|
selected_accounts
|
|
|
|
.iter()
|
|
|
|
.map(|a| a.lock().unwrap().account_name.clone())
|
|
|
|
.collect::<Vec<String>>()
|
|
|
|
.join(", ")
|
|
|
|
);
|
|
|
|
|
2021-08-14 17:10:21 +02:00
|
|
|
match tui::prompt_char("Do you want to continue?", "yN") {
|
|
|
|
'y' => {}
|
2021-08-12 01:39:29 +02:00
|
|
|
_ => {
|
2021-08-14 17:10:21 +02:00
|
|
|
info!("Aborting!");
|
2021-08-12 01:39:29 +02:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
let mut successful = vec![];
|
|
|
|
for a in selected_accounts {
|
|
|
|
let account = a.lock().unwrap();
|
|
|
|
match account.remove_authenticator(None) {
|
|
|
|
Ok(success) => {
|
|
|
|
if success {
|
|
|
|
println!("Removed authenticator from {}", account.account_name);
|
|
|
|
successful.push(account.account_name.clone());
|
|
|
|
} else {
|
|
|
|
println!(
|
|
|
|
"Failed to remove authenticator from {}",
|
|
|
|
account.account_name
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
Err(err) => {
|
|
|
|
println!(
|
|
|
|
"Unexpected error when removing authenticator from {}: {}",
|
|
|
|
account.account_name, err
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
for account_name in successful {
|
|
|
|
manifest.remove_account(account_name);
|
|
|
|
}
|
|
|
|
|
|
|
|
manifest.save().expect("Failed to save manifest.");
|
2021-08-08 18:54:46 +02:00
|
|
|
} else {
|
|
|
|
let server_time = steamapi::get_server_time();
|
|
|
|
for account in selected_accounts {
|
|
|
|
trace!("{:?}", account);
|
|
|
|
let code = account.lock().unwrap().generate_code(server_time);
|
|
|
|
println!("{}", code);
|
|
|
|
}
|
|
|
|
}
|
2021-03-22 02:21:29 +01:00
|
|
|
}
|
2021-04-04 16:40:16 +02:00
|
|
|
|
2021-07-27 22:24:56 +02:00
|
|
|
fn do_login(account: &mut SteamGuardAccount) {
|
2021-08-08 18:54:46 +02:00
|
|
|
if account.account_name.len() > 0 {
|
|
|
|
println!("Username: {}", account.account_name);
|
|
|
|
} else {
|
|
|
|
print!("Username: ");
|
2021-08-14 16:01:25 +02:00
|
|
|
account.account_name = tui::prompt();
|
2021-08-08 18:54:46 +02:00
|
|
|
}
|
|
|
|
let _ = std::io::stdout().flush();
|
|
|
|
let password = rpassword::prompt_password_stdout("Password: ").unwrap();
|
|
|
|
if password.len() > 0 {
|
|
|
|
debug!("password is present");
|
|
|
|
} else {
|
|
|
|
debug!("password is empty");
|
|
|
|
}
|
|
|
|
// TODO: reprompt if password is empty
|
|
|
|
let mut login = UserLogin::new(account.account_name.clone(), password);
|
|
|
|
let mut loops = 0;
|
|
|
|
loop {
|
|
|
|
match login.login() {
|
|
|
|
Ok(s) => {
|
|
|
|
account.session = Option::Some(s);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
Err(LoginError::Need2FA) => {
|
|
|
|
debug!("generating 2fa code and retrying");
|
|
|
|
let server_time = steamapi::get_server_time();
|
|
|
|
login.twofactor_code = account.generate_code(server_time);
|
|
|
|
}
|
|
|
|
Err(LoginError::NeedCaptcha { captcha_gid }) => {
|
|
|
|
debug!("need captcha to log in");
|
2021-08-14 16:01:25 +02:00
|
|
|
login.captcha_text = tui::prompt_captcha_text(&captcha_gid);
|
2021-08-08 18:54:46 +02:00
|
|
|
}
|
|
|
|
Err(LoginError::NeedEmail) => {
|
|
|
|
println!("You should have received an email with a code.");
|
|
|
|
print!("Enter code");
|
2021-08-14 16:01:25 +02:00
|
|
|
login.email_code = tui::prompt();
|
2021-08-08 18:54:46 +02:00
|
|
|
}
|
|
|
|
r => {
|
|
|
|
error!("Fatal login result: {:?}", r);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
loops += 1;
|
|
|
|
if loops > 2 {
|
|
|
|
error!("Too many loops. Aborting login process, to avoid getting rate limited.");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
2021-07-27 22:24:56 +02:00
|
|
|
}
|
2021-07-30 01:42:45 +02:00
|
|
|
|
2021-08-09 00:32:50 +02:00
|
|
|
fn do_login_raw() -> anyhow::Result<steamapi::Session> {
|
|
|
|
print!("Username: ");
|
2021-08-14 16:01:25 +02:00
|
|
|
let username = tui::prompt();
|
2021-08-09 00:32:50 +02:00
|
|
|
let _ = std::io::stdout().flush();
|
|
|
|
let password = rpassword::prompt_password_stdout("Password: ").unwrap();
|
|
|
|
if password.len() > 0 {
|
|
|
|
debug!("password is present");
|
|
|
|
} else {
|
|
|
|
debug!("password is empty");
|
|
|
|
}
|
|
|
|
// TODO: reprompt if password is empty
|
|
|
|
let mut login = UserLogin::new(username, password);
|
|
|
|
let mut loops = 0;
|
|
|
|
loop {
|
|
|
|
match login.login() {
|
|
|
|
Ok(s) => {
|
|
|
|
return Ok(s);
|
|
|
|
}
|
|
|
|
Err(LoginError::Need2FA) => {
|
|
|
|
print!("Enter 2fa code: ");
|
2021-08-14 16:01:25 +02:00
|
|
|
login.twofactor_code = tui::prompt();
|
2021-08-09 00:32:50 +02:00
|
|
|
}
|
|
|
|
Err(LoginError::NeedCaptcha { captcha_gid }) => {
|
|
|
|
debug!("need captcha to log in");
|
2021-08-14 16:01:25 +02:00
|
|
|
login.captcha_text = tui::prompt_captcha_text(&captcha_gid);
|
2021-08-09 00:32:50 +02:00
|
|
|
}
|
|
|
|
Err(LoginError::NeedEmail) => {
|
|
|
|
println!("You should have received an email with a code.");
|
|
|
|
print!("Enter code: ");
|
2021-08-14 16:01:25 +02:00
|
|
|
login.email_code = tui::prompt();
|
2021-08-09 00:32:50 +02:00
|
|
|
}
|
|
|
|
Err(r) => {
|
|
|
|
error!("Fatal login result: {:?}", r);
|
|
|
|
bail!(r);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
loops += 1;
|
|
|
|
if loops > 2 {
|
|
|
|
error!("Too many loops. Aborting login process, to avoid getting rate limited.");
|
|
|
|
bail!("Too many loops. Login process aborted to avoid getting rate limited.");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-08-13 00:36:03 +02:00
|
|
|
fn get_mafiles_dir() -> String {
|
2021-08-13 00:06:18 +02:00
|
|
|
let paths = vec![
|
|
|
|
Path::new(&dirs::config_dir().unwrap()).join("steamguard-cli/maFiles"),
|
|
|
|
Path::new(&dirs::home_dir().unwrap()).join("maFiles"),
|
|
|
|
];
|
|
|
|
|
|
|
|
for path in &paths {
|
|
|
|
if path.join("manifest.json").is_file() {
|
|
|
|
return path.to_str().unwrap().into();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return paths[0].to_str().unwrap().into();
|
|
|
|
}
|