add incomplete account linker

This commit is contained in:
Carson McManus 2021-07-27 16:24:56 -04:00
parent c936d9a0ac
commit d8ffc179e0
2 changed files with 152 additions and 30 deletions

View file

@ -0,0 +1,86 @@
use std::collections::HashMap;
use reqwest::{Url, cookie::{CookieStore}, header::COOKIE, header::{SET_COOKIE, USER_AGENT}};
use serde::{Serialize, Deserialize};
use serde_json::Value;
use steamguard_cli::{SteamGuardAccount, steamapi::Session};
use log::*;
#[derive(Debug, Clone)]
pub struct AccountLinker {
device_id: String,
phone_number: String,
pub account: SteamGuardAccount,
client: reqwest::blocking::Client,
}
impl AccountLinker {
pub fn new() -> AccountLinker {
return AccountLinker{
device_id: generate_device_id(),
phone_number: String::from(""),
account: SteamGuardAccount::new(),
client: reqwest::blocking::ClientBuilder::new()
.cookie_store(true)
.build()
.unwrap(),
}
}
pub fn link(&self, session: &mut Session) {
let mut params = HashMap::new();
params.insert("access_token", session.token.clone());
params.insert("steamid", session.steam_id.to_string());
params.insert("device_identifier", self.device_id.clone());
params.insert("authenticator_type", String::from("1"));
params.insert("sms_phone_id", String::from("1"));
}
fn has_phone(&self, session: &Session) -> bool {
return self._phoneajax(session, "has_phone", "null");
}
fn _phoneajax(&self, session: &Session, op: &str, arg: &str) -> bool {
trace!("_phoneajax: op={}", op);
let url = "https://steamcommunity.com".parse::<Url>().unwrap();
let cookies = reqwest::cookie::Jar::default();
cookies.add_cookie_str("mobileClientVersion=0 (2.1.3)", &url);
cookies.add_cookie_str("mobileClient=android", &url);
cookies.add_cookie_str("Steam_Language=english", &url);
let mut params = HashMap::new();
params.insert("op", op);
params.insert("arg", arg);
params.insert("sessionid", session.session_id.as_str());
if op == "check_sms_code" {
params.insert("checkfortos", "0");
params.insert("skipvoip", "1");
}
let resp = self.client
.post("https://steamcommunity.com/steamguard/phoneajax")
.header(COOKIE, cookies.cookies(&url).unwrap())
.send()
.unwrap();
let result: Value = resp.json().unwrap();
if result["has_phone"] != Value::Null {
trace!("found has_phone field");
return result["has_phone"].as_bool().unwrap();
} else if result["success"] != Value::Null {
trace!("found success field");
return result["success"].as_bool().unwrap();
} else {
trace!("did not find any expected field");
return false;
}
}
}
fn generate_device_id() -> String {
return format!("android:{}", uuid::Uuid::new_v4().to_string());
}
#[derive(Debug, Clone, Deserialize)]
pub struct AddAuthenticatorResponse {
pub response: SteamGuardAccount
}

View file

@ -1,6 +1,7 @@
extern crate rpassword; extern crate rpassword;
use borrow::BorrowMut; use borrow::BorrowMut;
use io::Write; use io::Write;
use steamapi::Session;
use steamguard_cli::*; use steamguard_cli::*;
use ::std::*; use ::std::*;
use text_io::read; use text_io::read;
@ -12,6 +13,7 @@ use regex::Regex;
#[macro_use] #[macro_use]
extern crate lazy_static; extern crate lazy_static;
mod accountmanager; mod accountmanager;
mod accountlinker;
lazy_static! { lazy_static! {
static ref CAPTCHA_VALID_CHARS: Regex = Regex::new("^([A-H]|[J-N]|[P-R]|[T-Z]|[2-4]|[7-9]|[@%&])+$").unwrap(); static ref CAPTCHA_VALID_CHARS: Regex = Regex::new("^([A-H]|[J-N]|[P-R]|[T-Z]|[2-4]|[7-9]|[@%&])+$").unwrap();
@ -67,6 +69,10 @@ fn main() {
.help("Accept all open trade confirmations. Does not open interactive interface.") .help("Accept all open trade confirmations. Does not open interactive interface.")
) )
) )
.subcommand(
App::new("setup")
.about("Set up a new account with steamguard-cli")
)
.get_matches(); .get_matches();
@ -88,6 +94,15 @@ fn main() {
} }
manifest.load_accounts(); manifest.load_accounts();
if matches.is_present("setup") {
info!("setup");
let mut linker = accountlinker::AccountLinker::new();
do_login(&mut linker.account);
// linker.link(linker.account.session.expect("no login session"));
return;
}
let mut selected_accounts: Vec<SteamGuardAccount> = vec![]; let mut selected_accounts: Vec<SteamGuardAccount> = vec![];
if matches.is_present("all") { if matches.is_present("all") {
// manifest.accounts.iter().map(|a| selected_accounts.push(a.b)); // manifest.accounts.iter().map(|a| selected_accounts.push(a.b));
@ -111,36 +126,9 @@ fn main() {
if matches.is_present("trade") { if matches.is_present("trade") {
info!("trade"); info!("trade");
for account in selected_accounts.iter_mut() { for a in selected_accounts.iter_mut() {
let _ = std::io::stdout().flush(); let mut account = a; // why is this necessary?
let password = rpassword::prompt_password_stdout("Password: ").unwrap(); do_login(&mut account);
trace!("password: {}", password);
let mut login = steamapi::UserLogin::new(account.account_name.clone(), password);
let mut loops = 0;
loop {
match login.login() {
steamapi::LoginResult::Ok(s) => {
account.session = Option::Some(s);
break;
}
steamapi::LoginResult::Need2FA => {
let server_time = steamapi::get_server_time();
login.twofactor_code = account.generate_code(server_time);
}
steamapi::LoginResult::NeedCaptcha{ captcha_gid } => {
login.captcha_text = prompt_captcha_text(&captcha_gid);
}
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;
}
}
info!("Checking for trade confirmations"); info!("Checking for trade confirmations");
account.get_trade_confirmations(); account.get_trade_confirmations();
@ -198,3 +186,51 @@ fn prompt_captcha_text(captcha_gid: &String) -> String {
} }
return captcha_text; return captcha_text;
} }
fn do_login(account: &mut SteamGuardAccount) {
if account.account_name.len() > 0 {
println!("Username: {}", account.account_name);
} else {
print!("Username: ");
account.account_name = prompt();
}
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 = steamapi::UserLogin::new(account.account_name.clone(), password);
let mut loops = 0;
loop {
match login.login() {
steamapi::LoginResult::Ok(s) => {
account.session = Option::Some(s);
break;
}
steamapi::LoginResult::Need2FA => {
let server_time = steamapi::get_server_time();
login.twofactor_code = account.generate_code(server_time);
}
steamapi::LoginResult::NeedCaptcha{ captcha_gid } => {
login.captcha_text = prompt_captcha_text(&captcha_gid);
}
steamapi::LoginResult::NeedEmail => {
println!("You should have received an email with a code.");
print!("Enter code");
login.email_code = prompt();
}
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;
}
}
}