Run "cargo fmt"

This commit is contained in:
Tilo Spannagel 2021-08-01 12:43:18 +00:00 committed by GitHub
parent 8567a06f54
commit f3910790bb
6 changed files with 1071 additions and 936 deletions

View file

@ -1,9 +1,9 @@
use std::collections::HashMap; use log::*;
use reqwest::{Url, cookie::{CookieStore}, header::COOKIE}; use reqwest::{cookie::CookieStore, header::COOKIE, Url};
use serde::Deserialize; use serde::Deserialize;
use serde_json::Value; use serde_json::Value;
use steamguard::{SteamGuardAccount, steamapi::Session}; use std::collections::HashMap;
use log::*; use steamguard::{steamapi::Session, SteamGuardAccount};
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct AccountLinker { pub struct AccountLinker {
@ -15,7 +15,7 @@ pub struct AccountLinker {
impl AccountLinker { impl AccountLinker {
pub fn new() -> AccountLinker { pub fn new() -> AccountLinker {
return AccountLinker{ return AccountLinker {
device_id: generate_device_id(), device_id: generate_device_id(),
phone_number: String::from(""), phone_number: String::from(""),
account: SteamGuardAccount::new(), account: SteamGuardAccount::new(),
@ -23,7 +23,7 @@ impl AccountLinker {
.cookie_store(true) .cookie_store(true)
.build() .build()
.unwrap(), .unwrap(),
} };
} }
pub fn link(&self, session: &mut Session) { pub fn link(&self, session: &mut Session) {
@ -56,7 +56,8 @@ impl AccountLinker {
params.insert("skipvoip", "1"); params.insert("skipvoip", "1");
} }
let resp = self.client let resp = self
.client
.post("https://steamcommunity.com/steamguard/phoneajax") .post("https://steamcommunity.com/steamguard/phoneajax")
.header(COOKIE, cookies.cookies(&url).unwrap()) .header(COOKIE, cookies.cookies(&url).unwrap())
.send() .send()
@ -82,5 +83,5 @@ fn generate_device_id() -> String {
#[derive(Debug, Clone, Deserialize)] #[derive(Debug, Clone, Deserialize)]
pub struct AddAuthenticatorResponse { pub struct AddAuthenticatorResponse {
pub response: SteamGuardAccount pub response: SteamGuardAccount,
} }

View file

@ -1,10 +1,10 @@
use log::*;
use serde::{Deserialize, Serialize};
use std::error::Error;
use std::fs::File; use std::fs::File;
use std::io::BufReader; use std::io::BufReader;
use std::path::Path; use std::path::Path;
use serde::{Serialize, Deserialize};
use std::error::Error;
use steamguard::SteamGuardAccount; use steamguard::SteamGuardAccount;
use log::*;
#[derive(Debug, Serialize, Deserialize)] #[derive(Debug, Serialize, Deserialize)]
pub struct Manifest { pub struct Manifest {

View file

@ -1,19 +1,28 @@
extern crate rpassword; extern crate rpassword;
use steamguard::{SteamGuardAccount, Confirmation, ConfirmationType, steamapi}; use clap::{crate_version, App, Arg};
use std::collections::HashSet;
use std::{io::{Write, stdout, stdin}, path::Path};
use clap::{App, Arg, crate_version};
use log::*; use log::*;
use regex::Regex; use regex::Regex;
use termion::{raw::IntoRawMode, screen::AlternateScreen, event::{Key, Event}, input::{TermRead}}; use std::collections::HashSet;
use std::{
io::{stdin, stdout, Write},
path::Path,
};
use steamguard::{steamapi, Confirmation, ConfirmationType, SteamGuardAccount};
use termion::{
event::{Event, Key},
input::TermRead,
raw::IntoRawMode,
screen::AlternateScreen,
};
#[macro_use] #[macro_use]
extern crate lazy_static; extern crate lazy_static;
mod accountmanager;
mod accountlinker; mod accountlinker;
mod accountmanager;
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();
} }
fn main() { fn main() {
@ -80,11 +89,12 @@ fn main() {
) )
.get_matches(); .get_matches();
let verbosity = matches.occurrences_of("verbosity") as usize + 2; let verbosity = matches.occurrences_of("verbosity") as usize + 2;
stderrlog::new() stderrlog::new()
.verbosity(verbosity) .verbosity(verbosity)
.module(module_path!()).init().unwrap(); .module(module_path!())
.init()
.unwrap();
if let Some(demo_matches) = matches.subcommand_matches("debug") { if let Some(demo_matches) = matches.subcommand_matches("debug") {
if demo_matches.is_present("demo-conf-menu") { if demo_matches.is_present("demo-conf-menu") {
@ -134,7 +144,13 @@ fn main() {
} }
} }
debug!("selected accounts: {:?}", selected_accounts.iter().map(|a| a.account_name.clone()).collect::<Vec<String>>()); debug!(
"selected accounts: {:?}",
selected_accounts
.iter()
.map(|a| a.account_name.clone())
.collect::<Vec<String>>()
);
if let Some(trade_matches) = matches.subcommand_matches("trade") { if let Some(trade_matches) = matches.subcommand_matches("trade") {
info!("trade"); info!("trade");
@ -162,8 +178,7 @@ fn main() {
let result = account.accept_confirmation(conf); let result = account.accept_confirmation(conf);
debug!("accept confirmation result: {:?}", result); debug!("accept confirmation result: {:?}", result);
} }
} } else {
else {
if termion::is_tty(&stdout()) { if termion::is_tty(&stdout()) {
let (accept, deny) = prompt_confirmation_menu(confirmations); let (accept, deny) = prompt_confirmation_menu(confirmations);
for conf in &accept { for conf in &accept {
@ -174,8 +189,7 @@ fn main() {
let result = account.deny_confirmation(conf); let result = account.deny_confirmation(conf);
debug!("deny confirmation result: {:?}", result); debug!("deny confirmation result: {:?}", result);
} }
} } else {
else {
warn!("not a tty, not showing menu"); warn!("not a tty, not showing menu");
for conf in &confirmations { for conf in &confirmations {
println!("{}", conf.description()); println!("{}", conf.description());
@ -219,7 +233,9 @@ fn test_validate_captcha_text() {
fn prompt() -> String { fn prompt() -> String {
let mut text = String::new(); let mut text = String::new();
let _ = std::io::stdout().flush(); let _ = std::io::stdout().flush();
stdin().read_line(&mut text).expect("Did not enter a correct string"); stdin()
.read_line(&mut text)
.expect("Did not enter a correct string");
return String::from(text.strip_suffix('\n').unwrap()); return String::from(text.strip_suffix('\n').unwrap());
} }
@ -238,7 +254,9 @@ fn prompt_captcha_text(captcha_gid: &String) -> String {
} }
/// Returns a tuple of (accepted, denied). Ignored confirmations are not included. /// Returns a tuple of (accepted, denied). Ignored confirmations are not included.
fn prompt_confirmation_menu(confirmations: Vec<Confirmation>) -> (Vec<Confirmation>, Vec<Confirmation>) { fn prompt_confirmation_menu(
confirmations: Vec<Confirmation>,
) -> (Vec<Confirmation>, Vec<Confirmation>) {
println!("press a key other than enter to show the menu."); println!("press a key other than enter to show the menu.");
let mut to_accept_idx: HashSet<usize> = HashSet::new(); let mut to_accept_idx: HashSet<usize> = HashSet::new();
let mut to_deny_idx: HashSet<usize> = HashSet::new(); let mut to_deny_idx: HashSet<usize> = HashSet::new();
@ -263,13 +281,22 @@ fn prompt_confirmation_menu(confirmations: Vec<Confirmation>) -> (Vec<Confirmati
to_deny_idx.remove(&selected_idx); to_deny_idx.remove(&selected_idx);
} }
Event::Key(Key::Char('A')) => { Event::Key(Key::Char('A')) => {
(0..confirmations.len()).for_each(|i| { to_accept_idx.insert(i); to_deny_idx.remove(&i); }); (0..confirmations.len()).for_each(|i| {
to_accept_idx.insert(i);
to_deny_idx.remove(&i);
});
} }
Event::Key(Key::Char('D')) => { Event::Key(Key::Char('D')) => {
(0..confirmations.len()).for_each(|i| { to_accept_idx.remove(&i); to_deny_idx.insert(i); }); (0..confirmations.len()).for_each(|i| {
to_accept_idx.remove(&i);
to_deny_idx.insert(i);
});
} }
Event::Key(Key::Char('I')) => { Event::Key(Key::Char('I')) => {
(0..confirmations.len()).for_each(|i| { to_accept_idx.remove(&i); to_deny_idx.remove(&i); }); (0..confirmations.len()).for_each(|i| {
to_accept_idx.remove(&i);
to_deny_idx.remove(&i);
});
} }
Event::Key(Key::Up) if selected_idx > 0 => { Event::Key(Key::Up) if selected_idx > 0 => {
selected_idx -= 1; selected_idx -= 1;
@ -286,27 +313,51 @@ fn prompt_confirmation_menu(confirmations: Vec<Confirmation>) -> (Vec<Confirmati
_ => {} _ => {}
} }
write!(screen, "{}{}{}arrow keys to select, [a]ccept, [d]eny, [i]gnore, [enter] confirm choices\n\n", termion::clear::All, termion::cursor::Goto(1, 1), termion::color::Fg(termion::color::White)).unwrap(); write!(
screen,
"{}{}{}arrow keys to select, [a]ccept, [d]eny, [i]gnore, [enter] confirm choices\n\n",
termion::clear::All,
termion::cursor::Goto(1, 1),
termion::color::Fg(termion::color::White)
)
.unwrap();
for i in 0..confirmations.len() { for i in 0..confirmations.len() {
if selected_idx == i { if selected_idx == i {
write!(screen, "\r{} >", termion::color::Fg(termion::color::LightYellow)).unwrap(); write!(
} screen,
else { "\r{} >",
termion::color::Fg(termion::color::LightYellow)
)
.unwrap();
} else {
write!(screen, "\r{} ", termion::color::Fg(termion::color::White)).unwrap(); write!(screen, "\r{} ", termion::color::Fg(termion::color::White)).unwrap();
} }
if to_accept_idx.contains(&i) { if to_accept_idx.contains(&i) {
write!(screen, "{}[a]", termion::color::Fg(termion::color::LightGreen)).unwrap(); write!(
} screen,
else if to_deny_idx.contains(&i) { "{}[a]",
write!(screen, "{}[d]", termion::color::Fg(termion::color::LightRed)).unwrap(); termion::color::Fg(termion::color::LightGreen)
} )
else { .unwrap();
} else if to_deny_idx.contains(&i) {
write!(
screen,
"{}[d]",
termion::color::Fg(termion::color::LightRed)
)
.unwrap();
} else {
write!(screen, "[ ]").unwrap(); write!(screen, "[ ]").unwrap();
} }
if selected_idx == i { if selected_idx == i {
write!(screen, "{}", termion::color::Fg(termion::color::LightYellow)).unwrap(); write!(
screen,
"{}",
termion::color::Fg(termion::color::LightYellow)
)
.unwrap();
} }
write!(screen, " {}\n", confirmations[i].description()).unwrap(); write!(screen, " {}\n", confirmations[i].description()).unwrap();
@ -346,7 +397,7 @@ fn do_login(account: &mut SteamGuardAccount) {
let server_time = steamapi::get_server_time(); let server_time = steamapi::get_server_time();
login.twofactor_code = account.generate_code(server_time); login.twofactor_code = account.generate_code(server_time);
} }
Err(steamapi::LoginError::NeedCaptcha{ captcha_gid }) => { Err(steamapi::LoginError::NeedCaptcha { captcha_gid }) => {
login.captcha_text = prompt_captcha_text(&captcha_gid); login.captcha_text = prompt_captcha_text(&captcha_gid);
} }
Err(steamapi::LoginError::NeedEmail) => { Err(steamapi::LoginError::NeedEmail) => {

View file

@ -21,7 +21,7 @@ pub enum ConfirmationType {
Trade = 2, Trade = 2,
MarketSell = 3, MarketSell = 3,
AccountRecovery = 6, AccountRecovery = 6,
Unknown Unknown,
} }
impl From<&str> for ConfirmationType { impl From<&str> for ConfirmationType {

View file

@ -1,18 +1,22 @@
use std::{collections::HashMap, convert::TryInto, thread, time};
use anyhow::Result; use anyhow::Result;
pub use confirmation::{Confirmation, ConfirmationType}; pub use confirmation::{Confirmation, ConfirmationType};
use hmacsha1::hmac_sha1; use hmacsha1::hmac_sha1;
use reqwest::{Url, cookie::CookieStore, header::{COOKIE, USER_AGENT}};
use serde::{Serialize, Deserialize};
use log::*; use log::*;
use reqwest::{
cookie::CookieStore,
header::{COOKIE, USER_AGENT},
Url,
};
use scraper::{Html, Selector}; use scraper::{Html, Selector};
use serde::{Deserialize, Serialize};
use std::{collections::HashMap, convert::TryInto, thread, time};
#[macro_use] #[macro_use]
extern crate lazy_static; extern crate lazy_static;
#[macro_use] #[macro_use]
extern crate anyhow; extern crate anyhow;
pub mod steamapi;
mod confirmation; mod confirmation;
pub mod steamapi;
// const STEAMAPI_BASE: String = "https://api.steampowered.com"; // const STEAMAPI_BASE: String = "https://api.steampowered.com";
// const COMMUNITY_BASE: String = "https://steamcommunity.com"; // const COMMUNITY_BASE: String = "https://steamcommunity.com";
@ -21,9 +25,9 @@ mod confirmation;
// const TWO_FACTOR_BASE: String = STEAMAPI_BASE + "/ITwoFactorService/%s/v0001"; // const TWO_FACTOR_BASE: String = STEAMAPI_BASE + "/ITwoFactorService/%s/v0001";
// static TWO_FACTOR_TIME_QUERY: String = TWO_FACTOR_BASE.Replace("%s", "QueryTime"); // static TWO_FACTOR_TIME_QUERY: String = TWO_FACTOR_BASE.Replace("%s", "QueryTime");
extern crate hmacsha1;
extern crate base64; extern crate base64;
extern crate cookie; extern crate cookie;
extern crate hmacsha1;
#[derive(Debug, Clone, Serialize, Deserialize)] #[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SteamGuardAccount { pub struct SteamGuardAccount {
@ -63,7 +67,7 @@ fn generate_confirmation_hash_for_time(time: i64, tag: &str, identity_secret: &S
impl SteamGuardAccount { impl SteamGuardAccount {
pub fn new() -> Self { pub fn new() -> Self {
return SteamGuardAccount{ return SteamGuardAccount {
account_name: String::from(""), account_name: String::from(""),
serial_number: String::from(""), serial_number: String::from(""),
revocation_code: String::from(""), revocation_code: String::from(""),
@ -75,11 +79,14 @@ impl SteamGuardAccount {
fully_enrolled: false, fully_enrolled: false,
device_id: String::from(""), device_id: String::from(""),
session: Option::None, session: Option::None,
} };
} }
pub fn generate_code(&self, time: i64) -> String { pub fn generate_code(&self, time: i64) -> String {
let steam_guard_code_translations: [u8; 26] = [50, 51, 52, 53, 54, 55, 56, 57, 66, 67, 68, 70, 71, 72, 74, 75, 77, 78, 80, 81, 82, 84, 86, 87, 88, 89]; let steam_guard_code_translations: [u8; 26] = [
50, 51, 52, 53, 54, 55, 56, 57, 66, 67, 68, 70, 71, 72, 74, 75, 77, 78, 80, 81, 82, 84,
86, 87, 88, 89,
];
// this effectively makes it so that it creates a new code every 30 seconds. // this effectively makes it so that it creates a new code every 30 seconds.
let time_bytes: [u8; 8] = build_time_bytes(time / 30i64); let time_bytes: [u8; 8] = build_time_bytes(time / 30i64);
@ -87,18 +94,18 @@ impl SteamGuardAccount {
let hashed_data = hmacsha1::hmac_sha1(&shared_secret, &time_bytes); let hashed_data = hmacsha1::hmac_sha1(&shared_secret, &time_bytes);
let mut code_array: [u8; 5] = [0; 5]; let mut code_array: [u8; 5] = [0; 5];
let b = (hashed_data[19] & 0xF) as usize; let b = (hashed_data[19] & 0xF) as usize;
let mut code_point: i32 = let mut code_point: i32 = ((hashed_data[b] & 0x7F) as i32) << 24
((hashed_data[b] & 0x7F) as i32) << 24 | | ((hashed_data[b + 1] & 0xFF) as i32) << 16
((hashed_data[b + 1] & 0xFF) as i32) << 16 | | ((hashed_data[b + 2] & 0xFF) as i32) << 8
((hashed_data[b + 2] & 0xFF) as i32) << 8 | | ((hashed_data[b + 3] & 0xFF) as i32);
((hashed_data[b + 3] & 0xFF) as i32);
for i in 0..5 { for i in 0..5 {
code_array[i] = steam_guard_code_translations[code_point as usize % steam_guard_code_translations.len()]; code_array[i] = steam_guard_code_translations
[code_point as usize % steam_guard_code_translations.len()];
code_point /= steam_guard_code_translations.len() as i32; code_point /= steam_guard_code_translations.len() as i32;
} }
return String::from_utf8(code_array.iter().map(|c| *c).collect()).unwrap() return String::from_utf8(code_array.iter().map(|c| *c).collect()).unwrap();
} }
fn get_confirmation_query_params(&self, tag: &str) -> HashMap<&str, String> { fn get_confirmation_query_params(&self, tag: &str) -> HashMap<&str, String> {
@ -107,7 +114,10 @@ impl SteamGuardAccount {
let mut params = HashMap::new(); let mut params = HashMap::new();
params.insert("p", self.device_id.clone()); params.insert("p", self.device_id.clone());
params.insert("a", session.steam_id.to_string()); params.insert("a", session.steam_id.to_string());
params.insert("k", generate_confirmation_hash_for_time(time, tag, &self.identity_secret)); params.insert(
"k",
generate_confirmation_hash_for_time(time, tag, &self.identity_secret),
);
params.insert("t", time.to_string()); params.insert("t", time.to_string());
params.insert("m", String::from("android")); params.insert("m", String::from("android"));
params.insert("tag", String::from(tag)); params.insert("tag", String::from(tag));
@ -126,7 +136,10 @@ impl SteamGuardAccount {
cookies.add_cookie_str(format!("sessionid={}", session_id).as_str(), &url); cookies.add_cookie_str(format!("sessionid={}", session_id).as_str(), &url);
cookies.add_cookie_str(format!("steamid={}", session.steam_id).as_str(), &url); cookies.add_cookie_str(format!("steamid={}", session.steam_id).as_str(), &url);
cookies.add_cookie_str(format!("steamLogin={}", session.steam_login).as_str(), &url); cookies.add_cookie_str(format!("steamLogin={}", session.steam_login).as_str(), &url);
cookies.add_cookie_str(format!("steamLoginSecure={}", session.steam_login_secure).as_str(), &url); cookies.add_cookie_str(
format!("steamLoginSecure={}", session.steam_login_secure).as_str(),
&url,
);
return cookies; return cookies;
} }
@ -174,7 +187,7 @@ impl SteamGuardAccount {
#[derive(Debug, Clone, Copy, Deserialize)] #[derive(Debug, Clone, Copy, Deserialize)]
struct SendConfirmationResponse { struct SendConfirmationResponse {
pub success: bool pub success: bool,
} }
let resp: SendConfirmationResponse = client.get("https://steamcommunity.com/mobileconf/ajaxop".parse::<Url>().unwrap()) let resp: SendConfirmationResponse = client.get("https://steamcommunity.com/mobileconf/ajaxop".parse::<Url>().unwrap())
@ -242,7 +255,12 @@ fn parse_confirmations(text: String) -> anyhow::Result<Vec<Confirmation>> {
let conf = Confirmation { let conf = Confirmation {
id: elem.value().attr("data-confid").unwrap().parse()?, id: elem.value().attr("data-confid").unwrap().parse()?,
key: elem.value().attr("data-key").unwrap().parse()?, key: elem.value().attr("data-key").unwrap().parse()?,
conf_type: elem.value().attr("data-type").unwrap().try_into().unwrap_or(ConfirmationType::Unknown), conf_type: elem
.value()
.attr("data-type")
.unwrap()
.try_into()
.unwrap_or(ConfirmationType::Unknown),
creator: elem.value().attr("data-creator").unwrap().parse()?, creator: elem.value().attr("data-creator").unwrap().parse()?,
}; };
confirmations.push(conf); confirmations.push(conf);
@ -258,7 +276,11 @@ mod tests {
fn test_build_time_bytes() { fn test_build_time_bytes() {
let t1 = build_time_bytes(1617591917i64); let t1 = build_time_bytes(1617591917i64);
let t2: [u8; 8] = [0, 0, 0, 0, 96, 106, 126, 109]; let t2: [u8; 8] = [0, 0, 0, 0, 96, 106, 126, 109];
assert!(t1.iter().zip(t2.iter()).all(|(a,b)| a == b), "Arrays are not equal, got {:?}", t1); assert!(
t1.iter().zip(t2.iter()).all(|(a, b)| a == b),
"Arrays are not equal, got {:?}",
t1
);
} }
#[test] #[test]
@ -272,7 +294,14 @@ mod tests {
#[test] #[test]
fn test_generate_confirmation_hash_for_time() { fn test_generate_confirmation_hash_for_time() {
assert_eq!(generate_confirmation_hash_for_time(1617591917, "conf", &String::from("GQP46b73Ws7gr8GmZFR0sDuau5c=")), String::from("NaL8EIMhfy/7vBounJ0CvpKbrPk=")); assert_eq!(
generate_confirmation_hash_for_time(
1617591917,
"conf",
&String::from("GQP46b73Ws7gr8GmZFR0sDuau5c=")
),
String::from("NaL8EIMhfy/7vBounJ0CvpKbrPk=")
);
} }
#[test] #[test]
@ -280,35 +309,50 @@ mod tests {
let text = include_str!("fixtures/confirmations/multiple-confirmations.html"); let text = include_str!("fixtures/confirmations/multiple-confirmations.html");
let confirmations = parse_confirmations(text.into()).unwrap(); let confirmations = parse_confirmations(text.into()).unwrap();
assert_eq!(confirmations.len(), 5); assert_eq!(confirmations.len(), 5);
assert_eq!(confirmations[0], Confirmation { assert_eq!(
confirmations[0],
Confirmation {
id: 9890792058, id: 9890792058,
key: 15509106087034649470, key: 15509106087034649470,
conf_type: ConfirmationType::MarketSell, conf_type: ConfirmationType::MarketSell,
creator: 3392884950693131245, creator: 3392884950693131245,
}); }
assert_eq!(confirmations[1], Confirmation { );
assert_eq!(
confirmations[1],
Confirmation {
id: 9890791666, id: 9890791666,
key: 2661901169510258722, key: 2661901169510258722,
conf_type: ConfirmationType::MarketSell, conf_type: ConfirmationType::MarketSell,
creator: 3392884950693130525, creator: 3392884950693130525,
}); }
assert_eq!(confirmations[2], Confirmation { );
assert_eq!(
confirmations[2],
Confirmation {
id: 9890791241, id: 9890791241,
key: 15784514761287735229, key: 15784514761287735229,
conf_type: ConfirmationType::MarketSell, conf_type: ConfirmationType::MarketSell,
creator: 3392884950693129565, creator: 3392884950693129565,
}); }
assert_eq!(confirmations[3], Confirmation { );
assert_eq!(
confirmations[3],
Confirmation {
id: 9890790828, id: 9890790828,
key: 5049250785011653560, key: 5049250785011653560,
conf_type: ConfirmationType::MarketSell, conf_type: ConfirmationType::MarketSell,
creator: 3392884950693128685, creator: 3392884950693128685,
}); }
assert_eq!(confirmations[4], Confirmation { );
assert_eq!(
confirmations[4],
Confirmation {
id: 9890790159, id: 9890790159,
key: 6133112455066694993, key: 6133112455066694993,
conf_type: ConfirmationType::MarketSell, conf_type: ConfirmationType::MarketSell,
creator: 3392884950693127345, creator: 3392884950693127345,
}); }
);
} }
} }

View file

@ -1,9 +1,14 @@
use std::collections::HashMap;
use reqwest::{Url, cookie::{CookieStore}, header::COOKIE, header::{SET_COOKIE, USER_AGENT}};
use rsa::{PublicKey, RsaPublicKey};
use std::time::{SystemTime, UNIX_EPOCH};
use serde::{Serialize, Deserialize};
use log::*; use log::*;
use reqwest::{
cookie::CookieStore,
header::COOKIE,
header::{SET_COOKIE, USER_AGENT},
Url,
};
use rsa::{PublicKey, RsaPublicKey};
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::time::{SystemTime, UNIX_EPOCH};
#[derive(Debug, Clone, Deserialize)] #[derive(Debug, Clone, Deserialize)]
struct LoginResponse { struct LoginResponse {
@ -48,7 +53,7 @@ struct RsaResponse {
pub enum LoginError { pub enum LoginError {
BadRSA, BadRSA,
BadCredentials, BadCredentials,
NeedCaptcha{ captcha_gid: String }, NeedCaptcha { captcha_gid: String },
Need2FA, Need2FA,
NeedEmail, NeedEmail,
TooManyAttempts, TooManyAttempts,
@ -86,14 +91,15 @@ impl UserLogin {
.cookie_store(true) .cookie_store(true)
.build() .build()
.unwrap(), .unwrap(),
} };
} }
/// Updates the cookie jar with the session cookies by pinging steam servers. /// Updates the cookie jar with the session cookies by pinging steam servers.
fn update_session(&self) { fn update_session(&self) {
trace!("UserLogin::update_session"); trace!("UserLogin::update_session");
let url = "https://steamcommunity.com".parse::<Url>().unwrap(); let url = "https://steamcommunity.com".parse::<Url>().unwrap();
self.cookies.add_cookie_str("mobileClientVersion=0 (2.1.3)", &url); self.cookies
.add_cookie_str("mobileClientVersion=0 (2.1.3)", &url);
self.cookies.add_cookie_str("mobileClient=android", &url); self.cookies.add_cookie_str("mobileClient=android", &url);
self.cookies.add_cookie_str("Steam_Language=english", &url); self.cookies.add_cookie_str("Steam_Language=english", &url);
@ -114,7 +120,9 @@ impl UserLogin {
pub fn login(&mut self) -> anyhow::Result<Session, LoginError> { pub fn login(&mut self) -> anyhow::Result<Session, LoginError> {
trace!("UserLogin::login"); trace!("UserLogin::login");
if self.captcha_required && self.captcha_text.len() == 0 { if self.captcha_required && self.captcha_text.len() == 0 {
return Err(LoginError::NeedCaptcha{captcha_gid: self.captcha_gid.clone()}); return Err(LoginError::NeedCaptcha {
captcha_gid: self.captcha_gid.clone(),
});
} }
let url = "https://steamcommunity.com".parse::<Url>().unwrap(); let url = "https://steamcommunity.com".parse::<Url>().unwrap();
@ -123,9 +131,20 @@ impl UserLogin {
} }
let mut params = HashMap::new(); let mut params = HashMap::new();
params.insert("donotcache", format!("{}", SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_secs() * 1000)); params.insert(
"donotcache",
format!(
"{}",
SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap()
.as_secs()
* 1000
),
);
params.insert("username", self.username.clone()); params.insert("username", self.username.clone());
let resp = self.client let resp = self
.client
.post("https://steamcommunity.com/login/getrsakey") .post("https://steamcommunity.com/login/getrsakey")
.form(&params) .form(&params)
.send() .send()
@ -149,7 +168,17 @@ impl UserLogin {
trace!("twofactorcode: {}", self.twofactor_code); trace!("twofactorcode: {}", self.twofactor_code);
trace!("emailauth: {}", self.email_code); trace!("emailauth: {}", self.email_code);
let mut params = HashMap::new(); let mut params = HashMap::new();
params.insert("donotcache", format!("{}", SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_secs() * 1000)); params.insert(
"donotcache",
format!(
"{}",
SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap()
.as_secs()
* 1000
),
);
params.insert("username", self.username.clone()); params.insert("username", self.username.clone());
params.insert("password", encrypted_password); params.insert("password", encrypted_password);
params.insert("twofactorcode", self.twofactor_code.clone()); params.insert("twofactorcode", self.twofactor_code.clone());
@ -159,13 +188,18 @@ impl UserLogin {
params.insert("rsatimestamp", rsa_timestamp); params.insert("rsatimestamp", rsa_timestamp);
params.insert("remember_login", String::from("true")); params.insert("remember_login", String::from("true"));
params.insert("oauth_client_id", String::from("DE45CD61")); params.insert("oauth_client_id", String::from("DE45CD61"));
params.insert("oauth_scope", String::from("read_profile write_profile read_client write_client")); params.insert(
"oauth_scope",
String::from("read_profile write_profile read_client write_client"),
);
let login_resp: LoginResponse; let login_resp: LoginResponse;
match self.client match self
.client
.post("https://steamcommunity.com/login/dologin") .post("https://steamcommunity.com/login/dologin")
.form(&params) .form(&params)
.send() { .send()
{
Ok(resp) => { Ok(resp) => {
// https://stackoverflow.com/questions/49928648/rubys-mechanize-error-401-while-sending-a-post-request-steam-trade-offer-send // https://stackoverflow.com/questions/49928648/rubys-mechanize-error-401-while-sending-a-post-request-steam-trade-offer-send
let text = resp.text().unwrap(); let text = resp.text().unwrap();
@ -198,7 +232,9 @@ impl UserLogin {
if login_resp.captcha_needed { if login_resp.captcha_needed {
self.captcha_gid = login_resp.captcha_gid.clone(); self.captcha_gid = login_resp.captcha_gid.clone();
return Err(LoginError::NeedCaptcha{ captcha_gid: self.captcha_gid.clone() }); return Err(LoginError::NeedCaptcha {
captcha_gid: self.captcha_gid.clone(),
});
} }
if login_resp.emailauth_needed { if login_resp.emailauth_needed {
@ -214,7 +250,6 @@ impl UserLogin {
return Err(LoginError::BadCredentials); return Err(LoginError::BadCredentials);
} }
// transfer login parameters? Not completely sure what this is for. // transfer login parameters? Not completely sure what this is for.
// i guess steam changed their authentication scheme slightly // i guess steam changed their authentication scheme slightly
let oauth; let oauth;
@ -223,10 +258,7 @@ impl UserLogin {
debug!("received transfer parameters, relaying data..."); debug!("received transfer parameters, relaying data...");
for url in urls { for url in urls {
trace!("posting transfer to {}", url); trace!("posting transfer to {}", url);
let result = self.client let result = self.client.post(url).json(&params).send();
.post(url)
.json(&params)
.send();
trace!("result: {:?}", result); trace!("result: {:?}", result);
match result { match result {
Ok(resp) => { Ok(resp) => {
@ -257,7 +289,10 @@ impl UserLogin {
let cookies = self.cookies.cookies(&url).unwrap(); let cookies = self.cookies.cookies(&url).unwrap();
let all_cookies = cookies.to_str().unwrap(); let all_cookies = cookies.to_str().unwrap();
let mut session_id = String::from(""); let mut session_id = String::from("");
for cookie in all_cookies.split(";").map(|s| cookie::Cookie::parse(s).unwrap()) { for cookie in all_cookies
.split(";")
.map(|s| cookie::Cookie::parse(s).unwrap())
{
if cookie.name() == "sessionid" { if cookie.name() == "sessionid" {
session_id = String::from(cookie.value()); session_id = String::from(cookie.value());
} }
@ -269,7 +304,7 @@ impl UserLogin {
} }
fn build_session(&self, data: OAuthData, session_id: String) -> Session { fn build_session(&self, data: OAuthData, session_id: String) -> Session {
return Session{ return Session {
token: data.oauth_token, token: data.oauth_token,
steam_id: data.steamid, steam_id: data.steamid,
steam_login: format!("{}%7C%7C{}", data.steamid, data.wgtoken), steam_login: format!("{}%7C%7C{}", data.steamid, data.wgtoken),
@ -286,9 +321,7 @@ impl UserLogin {
for c in set_cookie_iter { for c in set_cookie_iter {
c.to_str() c.to_str()
.into_iter() .into_iter()
.for_each(|cookie_str| { .for_each(|cookie_str| self.cookies.add_cookie_str(cookie_str, &url));
self.cookies.add_cookie_str(cookie_str, &url)
});
} }
} }
} }
@ -326,7 +359,9 @@ pub fn get_server_time() -> i64 {
.send(); .send();
let value: serde_json::Value = resp.unwrap().json().unwrap(); let value: serde_json::Value = resp.unwrap().json().unwrap();
return String::from(value["response"]["server_time"].as_str().unwrap()).parse().unwrap(); return String::from(value["response"]["server_time"].as_str().unwrap())
.parse()
.unwrap();
} }
fn encrypt_password(rsa_resp: RsaResponse, password: &String) -> String { fn encrypt_password(rsa_resp: RsaResponse, password: &String) -> String {
@ -338,7 +373,11 @@ fn encrypt_password(rsa_resp: RsaResponse, password: &String) -> String {
#[cfg(not(test))] #[cfg(not(test))]
let mut rng = rand::rngs::OsRng; let mut rng = rand::rngs::OsRng;
let padding = rsa::PaddingScheme::new_pkcs1v15_encrypt(); let padding = rsa::PaddingScheme::new_pkcs1v15_encrypt();
let encrypted_password = base64::encode(public_key.encrypt(&mut rng, padding, password.as_bytes()).unwrap()); let encrypted_password = base64::encode(
public_key
.encrypt(&mut rng, padding, password.as_bytes())
.unwrap(),
);
return encrypted_password; return encrypted_password;
} }