Merge pull request #72 from tilosp/rustfmt
Run cargo fmt and check it using github actions
This commit is contained in:
commit
258e758395
7 changed files with 1096 additions and 936 deletions
25
.github/workflows/rustfmt.yml
vendored
Normal file
25
.github/workflows/rustfmt.yml
vendored
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
on: [push, pull_request]
|
||||||
|
|
||||||
|
name: rustfmt
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
rustfmt:
|
||||||
|
name: rustfmt
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout sources
|
||||||
|
uses: actions/checkout@v2
|
||||||
|
|
||||||
|
- name: Install stable toolchain
|
||||||
|
uses: actions-rs/toolchain@v1
|
||||||
|
with:
|
||||||
|
profile: minimal
|
||||||
|
toolchain: stable
|
||||||
|
override: true
|
||||||
|
components: rustfmt
|
||||||
|
|
||||||
|
- name: Run cargo fmt
|
||||||
|
uses: actions-rs/cargo@v1
|
||||||
|
with:
|
||||||
|
command: fmt
|
||||||
|
args: --all -- --check
|
|
@ -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,
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
113
src/main.rs
113
src/main.rs
|
@ -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) => {
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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,
|
||||||
});
|
}
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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(¶ms)
|
.form(¶ms)
|
||||||
.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(¶ms)
|
.form(¶ms)
|
||||||
.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(¶ms).send();
|
||||||
.post(url)
|
|
||||||
.json(¶ms)
|
|
||||||
.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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue