add HTTP proxy support (#264)
- allow extracting the http client from web api transport - refactor most commands so that they can accept an external transport - update confirmer to use transport's http client - update remove command - update TwoFactorClient so it doesn't need to be mutable - update get_server_time so it requires a transport - update remove_authenticator - update login proceedure to use given transport - update usages of do_login - update setup - update trade - update code command - update qr-login command - make borrowcheck happy - make WebApiTransport Clone and remove Default impl - remove dead code - fix lints closes #177
This commit is contained in:
parent
1d99bae469
commit
fe663cf43f
17 changed files with 164 additions and 105 deletions
|
@ -4,7 +4,7 @@ use clap::{clap_derive::ArgEnum, Parser};
|
||||||
use clap_complete::Shell;
|
use clap_complete::Shell;
|
||||||
use secrecy::SecretString;
|
use secrecy::SecretString;
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
use steamguard::SteamGuardAccount;
|
use steamguard::{transport::Transport, SteamGuardAccount};
|
||||||
|
|
||||||
use crate::AccountManager;
|
use crate::AccountManager;
|
||||||
|
|
||||||
|
@ -40,23 +40,33 @@ pub(crate) trait ConstCommand {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A command that operates the manifest as a whole
|
/// A command that operates the manifest as a whole
|
||||||
pub(crate) trait ManifestCommand {
|
pub(crate) trait ManifestCommand<T>
|
||||||
fn execute(&self, manager: &mut AccountManager) -> anyhow::Result<()>;
|
where
|
||||||
|
T: Transport,
|
||||||
|
{
|
||||||
|
fn execute(&self, transport: T, manager: &mut AccountManager) -> anyhow::Result<()>;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A command that operates on individual accounts.
|
/// A command that operates on individual accounts.
|
||||||
pub(crate) trait AccountCommand {
|
pub(crate) trait AccountCommand<T>
|
||||||
|
where
|
||||||
|
T: Transport,
|
||||||
|
{
|
||||||
fn execute(
|
fn execute(
|
||||||
&self,
|
&self,
|
||||||
|
transport: T,
|
||||||
manager: &mut AccountManager,
|
manager: &mut AccountManager,
|
||||||
accounts: Vec<Arc<Mutex<SteamGuardAccount>>>,
|
accounts: Vec<Arc<Mutex<SteamGuardAccount>>>,
|
||||||
) -> anyhow::Result<()>;
|
) -> anyhow::Result<()>;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) enum CommandType {
|
pub(crate) enum CommandType<T>
|
||||||
|
where
|
||||||
|
T: Transport,
|
||||||
|
{
|
||||||
Const(Box<dyn ConstCommand>),
|
Const(Box<dyn ConstCommand>),
|
||||||
Manifest(Box<dyn ManifestCommand>),
|
Manifest(Box<dyn ManifestCommand<T>>),
|
||||||
Account(Box<dyn AccountCommand>),
|
Account(Box<dyn AccountCommand<T>>),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Parser)]
|
#[derive(Debug, Clone, Parser)]
|
||||||
|
@ -114,6 +124,14 @@ pub(crate) struct GlobalArgs {
|
||||||
long_help = "Disable checking for updates. By default, steamguard-cli will check for updates every now and then. This can be disabled with this flag."
|
long_help = "Disable checking for updates. By default, steamguard-cli will check for updates every now and then. This can be disabled with this flag."
|
||||||
)]
|
)]
|
||||||
pub no_update_check: bool,
|
pub no_update_check: bool,
|
||||||
|
|
||||||
|
#[clap(
|
||||||
|
long,
|
||||||
|
env = "HTTP_PROXY",
|
||||||
|
help = "Use a proxy for HTTP requests.",
|
||||||
|
long_help = "Use a proxy for HTTP requests. This is useful if you are behind a firewall and need to use a proxy to access the internet."
|
||||||
|
)]
|
||||||
|
pub http_proxy: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Parser)]
|
#[derive(Debug, Clone, Parser)]
|
||||||
|
|
|
@ -20,16 +20,20 @@ pub struct CodeCommand {
|
||||||
pub offline: bool,
|
pub offline: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AccountCommand for CodeCommand {
|
impl<T> AccountCommand<T> for CodeCommand
|
||||||
|
where
|
||||||
|
T: Transport,
|
||||||
|
{
|
||||||
fn execute(
|
fn execute(
|
||||||
&self,
|
&self,
|
||||||
|
transport: T,
|
||||||
_manager: &mut AccountManager,
|
_manager: &mut AccountManager,
|
||||||
accounts: Vec<Arc<Mutex<SteamGuardAccount>>>,
|
accounts: Vec<Arc<Mutex<SteamGuardAccount>>>,
|
||||||
) -> anyhow::Result<()> {
|
) -> anyhow::Result<()> {
|
||||||
let server_time = if self.offline {
|
let server_time = if self.offline {
|
||||||
SystemTime::now().duration_since(UNIX_EPOCH)?.as_secs()
|
SystemTime::now().duration_since(UNIX_EPOCH)?.as_secs()
|
||||||
} else {
|
} else {
|
||||||
steamapi::get_server_time()?.server_time()
|
steamapi::get_server_time(transport)?.server_time()
|
||||||
};
|
};
|
||||||
debug!("Time used to generate codes: {}", server_time);
|
debug!("Time used to generate codes: {}", server_time);
|
||||||
|
|
||||||
|
|
|
@ -8,8 +8,11 @@ use super::*;
|
||||||
#[clap(about = "Decrypt all maFiles")]
|
#[clap(about = "Decrypt all maFiles")]
|
||||||
pub struct DecryptCommand;
|
pub struct DecryptCommand;
|
||||||
|
|
||||||
impl ManifestCommand for DecryptCommand {
|
impl<T> ManifestCommand<T> for DecryptCommand
|
||||||
fn execute(&self, manager: &mut AccountManager) -> anyhow::Result<()> {
|
where
|
||||||
|
T: Transport,
|
||||||
|
{
|
||||||
|
fn execute(&self, _transport: T, manager: &mut AccountManager) -> anyhow::Result<()> {
|
||||||
load_accounts_with_prompts(manager)?;
|
load_accounts_with_prompts(manager)?;
|
||||||
for mut entry in manager.iter_mut() {
|
for mut entry in manager.iter_mut() {
|
||||||
entry.encryption = None;
|
entry.encryption = None;
|
||||||
|
|
|
@ -8,8 +8,11 @@ use super::*;
|
||||||
#[clap(about = "Encrypt all maFiles")]
|
#[clap(about = "Encrypt all maFiles")]
|
||||||
pub struct EncryptCommand;
|
pub struct EncryptCommand;
|
||||||
|
|
||||||
impl ManifestCommand for EncryptCommand {
|
impl<T> ManifestCommand<T> for EncryptCommand
|
||||||
fn execute(&self, manager: &mut AccountManager) -> anyhow::Result<()> {
|
where
|
||||||
|
T: Transport,
|
||||||
|
{
|
||||||
|
fn execute(&self, _transport: T, manager: &mut AccountManager) -> anyhow::Result<()> {
|
||||||
if !manager.has_passkey() {
|
if !manager.has_passkey() {
|
||||||
let mut passkey;
|
let mut passkey;
|
||||||
loop {
|
loop {
|
||||||
|
|
|
@ -18,8 +18,11 @@ pub struct ImportCommand {
|
||||||
pub files: Vec<String>,
|
pub files: Vec<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ManifestCommand for ImportCommand {
|
impl<T> ManifestCommand<T> for ImportCommand
|
||||||
fn execute(&self, manager: &mut AccountManager) -> anyhow::Result<()> {
|
where
|
||||||
|
T: Transport,
|
||||||
|
{
|
||||||
|
fn execute(&self, _transport: T, manager: &mut AccountManager) -> anyhow::Result<()> {
|
||||||
for file_path in self.files.iter() {
|
for file_path in self.files.iter() {
|
||||||
if self.sda {
|
if self.sda {
|
||||||
let path = Path::new(&file_path);
|
let path = Path::new(&file_path);
|
||||||
|
|
|
@ -18,9 +18,13 @@ pub struct QrCommand {
|
||||||
pub ascii: bool,
|
pub ascii: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AccountCommand for QrCommand {
|
impl<T> AccountCommand<T> for QrCommand
|
||||||
|
where
|
||||||
|
T: Transport,
|
||||||
|
{
|
||||||
fn execute(
|
fn execute(
|
||||||
&self,
|
&self,
|
||||||
|
_transport: T,
|
||||||
_manager: &mut AccountManager,
|
_manager: &mut AccountManager,
|
||||||
accounts: Vec<Arc<Mutex<SteamGuardAccount>>>,
|
accounts: Vec<Arc<Mutex<SteamGuardAccount>>>,
|
||||||
) -> anyhow::Result<()> {
|
) -> anyhow::Result<()> {
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
use std::sync::{Arc, Mutex};
|
use std::sync::{Arc, Mutex};
|
||||||
|
|
||||||
use log::*;
|
use log::*;
|
||||||
use steamguard::{transport::WebApiTransport, QrApprover, QrApproverError};
|
use steamguard::{QrApprover, QrApproverError};
|
||||||
|
|
||||||
use crate::AccountManager;
|
use crate::AccountManager;
|
||||||
|
|
||||||
|
@ -17,9 +17,13 @@ pub struct QrLoginCommand {
|
||||||
pub url: String,
|
pub url: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AccountCommand for QrLoginCommand {
|
impl<T> AccountCommand<T> for QrLoginCommand
|
||||||
|
where
|
||||||
|
T: Transport + Clone,
|
||||||
|
{
|
||||||
fn execute(
|
fn execute(
|
||||||
&self,
|
&self,
|
||||||
|
transport: T,
|
||||||
_manager: &mut AccountManager,
|
_manager: &mut AccountManager,
|
||||||
accounts: Vec<Arc<Mutex<SteamGuardAccount>>>,
|
accounts: Vec<Arc<Mutex<SteamGuardAccount>>>,
|
||||||
) -> anyhow::Result<()> {
|
) -> anyhow::Result<()> {
|
||||||
|
@ -33,7 +37,7 @@ impl AccountCommand for QrLoginCommand {
|
||||||
info!("Approving login to {}", account.account_name);
|
info!("Approving login to {}", account.account_name);
|
||||||
|
|
||||||
if account.tokens.is_none() {
|
if account.tokens.is_none() {
|
||||||
crate::do_login(&mut account)?;
|
crate::do_login(transport.clone(), &mut account)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
|
@ -42,7 +46,7 @@ impl AccountCommand for QrLoginCommand {
|
||||||
return Err(anyhow!("No tokens found for {}", account.account_name));
|
return Err(anyhow!("No tokens found for {}", account.account_name));
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut approver = QrApprover::new(WebApiTransport::default(), tokens);
|
let mut approver = QrApprover::new(transport.clone(), tokens);
|
||||||
match approver.approve(&account, &self.url) {
|
match approver.approve(&account, &self.url) {
|
||||||
Ok(_) => {
|
Ok(_) => {
|
||||||
info!("Login approved.");
|
info!("Login approved.");
|
||||||
|
@ -50,7 +54,7 @@ impl AccountCommand for QrLoginCommand {
|
||||||
}
|
}
|
||||||
Err(QrApproverError::Unauthorized) => {
|
Err(QrApproverError::Unauthorized) => {
|
||||||
warn!("tokens are invalid. Attempting to log in again.");
|
warn!("tokens are invalid. Attempting to log in again.");
|
||||||
crate::do_login(&mut account)?;
|
crate::do_login(transport.clone(), &mut account)?;
|
||||||
}
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
error!("Failed to approve login: {}", e);
|
error!("Failed to approve login: {}", e);
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
use std::sync::{Arc, Mutex};
|
use std::sync::{Arc, Mutex};
|
||||||
|
|
||||||
use log::*;
|
use log::*;
|
||||||
use steamguard::{transport::TransportError, RemoveAuthenticatorError};
|
use steamguard::{steamapi::TwoFactorClient, transport::TransportError, RemoveAuthenticatorError};
|
||||||
|
|
||||||
use crate::{errors::UserError, tui, AccountManager};
|
use crate::{errors::UserError, tui, AccountManager};
|
||||||
|
|
||||||
|
@ -11,9 +11,13 @@ use super::*;
|
||||||
#[clap(about = "Remove the authenticator from an account.")]
|
#[clap(about = "Remove the authenticator from an account.")]
|
||||||
pub struct RemoveCommand;
|
pub struct RemoveCommand;
|
||||||
|
|
||||||
impl AccountCommand for RemoveCommand {
|
impl<T> AccountCommand<T> for RemoveCommand
|
||||||
|
where
|
||||||
|
T: Transport + Clone,
|
||||||
|
{
|
||||||
fn execute(
|
fn execute(
|
||||||
&self,
|
&self,
|
||||||
|
transport: T,
|
||||||
manager: &mut AccountManager,
|
manager: &mut AccountManager,
|
||||||
accounts: Vec<Arc<Mutex<SteamGuardAccount>>>,
|
accounts: Vec<Arc<Mutex<SteamGuardAccount>>>,
|
||||||
) -> anyhow::Result<()> {
|
) -> anyhow::Result<()> {
|
||||||
|
@ -38,10 +42,11 @@ impl AccountCommand for RemoveCommand {
|
||||||
let mut successful = vec![];
|
let mut successful = vec![];
|
||||||
for a in accounts {
|
for a in accounts {
|
||||||
let mut account = a.lock().unwrap();
|
let mut account = a.lock().unwrap();
|
||||||
|
let client = TwoFactorClient::new(transport.clone());
|
||||||
|
|
||||||
let mut revocation: Option<String> = None;
|
let mut revocation: Option<String> = None;
|
||||||
loop {
|
loop {
|
||||||
match account.remove_authenticator(revocation.as_ref()) {
|
match account.remove_authenticator(&client, revocation.as_ref()) {
|
||||||
Ok(_) => {
|
Ok(_) => {
|
||||||
info!("Removed authenticator from {}", account.account_name);
|
info!("Removed authenticator from {}", account.account_name);
|
||||||
successful.push(account.account_name.clone());
|
successful.push(account.account_name.clone());
|
||||||
|
@ -49,7 +54,7 @@ impl AccountCommand for RemoveCommand {
|
||||||
}
|
}
|
||||||
Err(RemoveAuthenticatorError::TransportError(TransportError::Unauthorized)) => {
|
Err(RemoveAuthenticatorError::TransportError(TransportError::Unauthorized)) => {
|
||||||
error!("Account {} is not logged in", account.account_name);
|
error!("Account {} is not logged in", account.account_name);
|
||||||
crate::do_login(&mut account)?;
|
crate::do_login(transport.clone(), &mut account)?;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
Err(RemoveAuthenticatorError::IncorrectRevocationCode {
|
Err(RemoveAuthenticatorError::IncorrectRevocationCode {
|
||||||
|
|
|
@ -3,7 +3,7 @@ use phonenumber::PhoneNumber;
|
||||||
use secrecy::ExposeSecret;
|
use secrecy::ExposeSecret;
|
||||||
use steamguard::{
|
use steamguard::{
|
||||||
accountlinker::AccountLinkSuccess, phonelinker::PhoneLinker, steamapi::PhoneClient,
|
accountlinker::AccountLinkSuccess, phonelinker::PhoneLinker, steamapi::PhoneClient,
|
||||||
token::Tokens, transport::WebApiTransport, AccountLinkError, AccountLinker, FinalizeLinkError,
|
token::Tokens, AccountLinkError, AccountLinker, FinalizeLinkError,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::{tui, AccountManager};
|
use crate::{tui, AccountManager};
|
||||||
|
@ -14,8 +14,11 @@ use super::*;
|
||||||
#[clap(about = "Set up a new account with steamguard-cli")]
|
#[clap(about = "Set up a new account with steamguard-cli")]
|
||||||
pub struct SetupCommand;
|
pub struct SetupCommand;
|
||||||
|
|
||||||
impl ManifestCommand for SetupCommand {
|
impl<T> ManifestCommand<T> for SetupCommand
|
||||||
fn execute(&self, manager: &mut AccountManager) -> anyhow::Result<()> {
|
where
|
||||||
|
T: Transport + Clone,
|
||||||
|
{
|
||||||
|
fn execute(&self, transport: T, manager: &mut AccountManager) -> anyhow::Result<()> {
|
||||||
eprintln!("Log in to the account that you want to link to steamguard-cli");
|
eprintln!("Log in to the account that you want to link to steamguard-cli");
|
||||||
eprint!("Username: ");
|
eprint!("Username: ");
|
||||||
let username = tui::prompt().to_lowercase();
|
let username = tui::prompt().to_lowercase();
|
||||||
|
@ -27,11 +30,11 @@ impl ManifestCommand for SetupCommand {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
info!("Logging in to {}", username);
|
info!("Logging in to {}", username);
|
||||||
let tokens =
|
let tokens = crate::do_login_raw(transport.clone(), username)
|
||||||
crate::do_login_raw(username).expect("Failed to log in. Account has not been linked.");
|
.expect("Failed to log in. Account has not been linked.");
|
||||||
|
|
||||||
info!("Adding authenticator...");
|
info!("Adding authenticator...");
|
||||||
let mut linker = AccountLinker::new(WebApiTransport::default(), tokens);
|
let mut linker = AccountLinker::new(transport.clone(), tokens);
|
||||||
let link: AccountLinkSuccess;
|
let link: AccountLinkSuccess;
|
||||||
loop {
|
loop {
|
||||||
match linker.link() {
|
match linker.link() {
|
||||||
|
@ -41,7 +44,7 @@ impl ManifestCommand for SetupCommand {
|
||||||
}
|
}
|
||||||
Err(AccountLinkError::MustProvidePhoneNumber) => {
|
Err(AccountLinkError::MustProvidePhoneNumber) => {
|
||||||
eprintln!("Looks like you don't have a phone number on this account.");
|
eprintln!("Looks like you don't have a phone number on this account.");
|
||||||
do_add_phone_number(linker.tokens())?;
|
do_add_phone_number(transport.clone(), linker.tokens())?;
|
||||||
}
|
}
|
||||||
Err(AccountLinkError::MustConfirmEmail) => {
|
Err(AccountLinkError::MustConfirmEmail) => {
|
||||||
println!("Check your email and click the link.");
|
println!("Check your email and click the link.");
|
||||||
|
@ -129,8 +132,8 @@ impl ManifestCommand for SetupCommand {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn do_add_phone_number(tokens: &Tokens) -> anyhow::Result<()> {
|
pub fn do_add_phone_number<T: Transport>(transport: T, tokens: &Tokens) -> anyhow::Result<()> {
|
||||||
let client = PhoneClient::new(WebApiTransport::default());
|
let client = PhoneClient::new(transport);
|
||||||
|
|
||||||
let linker = PhoneLinker::new(client, tokens.clone());
|
let linker = PhoneLinker::new(client, tokens.clone());
|
||||||
|
|
||||||
|
|
|
@ -25,9 +25,13 @@ pub struct TradeCommand {
|
||||||
pub fail_fast: bool,
|
pub fail_fast: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AccountCommand for TradeCommand {
|
impl<T> AccountCommand<T> for TradeCommand
|
||||||
|
where
|
||||||
|
T: Transport + Clone,
|
||||||
|
{
|
||||||
fn execute(
|
fn execute(
|
||||||
&self,
|
&self,
|
||||||
|
transport: T,
|
||||||
manager: &mut AccountManager,
|
manager: &mut AccountManager,
|
||||||
accounts: Vec<Arc<Mutex<SteamGuardAccount>>>,
|
accounts: Vec<Arc<Mutex<SteamGuardAccount>>>,
|
||||||
) -> anyhow::Result<()> {
|
) -> anyhow::Result<()> {
|
||||||
|
@ -36,13 +40,13 @@ impl AccountCommand for TradeCommand {
|
||||||
|
|
||||||
if !account.is_logged_in() {
|
if !account.is_logged_in() {
|
||||||
info!("Account does not have tokens, logging in");
|
info!("Account does not have tokens, logging in");
|
||||||
crate::do_login(&mut account)?;
|
crate::do_login(transport.clone(), &mut account)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
info!("{}: Checking for trade confirmations", account.account_name);
|
info!("{}: Checking for trade confirmations", account.account_name);
|
||||||
let confirmations: Vec<Confirmation>;
|
let confirmations: Vec<Confirmation>;
|
||||||
loop {
|
loop {
|
||||||
let confirmer = Confirmer::new(&account);
|
let confirmer = Confirmer::new(transport.clone(), &account);
|
||||||
|
|
||||||
match confirmer.get_trade_confirmations() {
|
match confirmer.get_trade_confirmations() {
|
||||||
Ok(confs) => {
|
Ok(confs) => {
|
||||||
|
@ -51,7 +55,7 @@ impl AccountCommand for TradeCommand {
|
||||||
}
|
}
|
||||||
Err(ConfirmerError::InvalidTokens) => {
|
Err(ConfirmerError::InvalidTokens) => {
|
||||||
info!("obtaining new tokens");
|
info!("obtaining new tokens");
|
||||||
crate::do_login(&mut account)?;
|
crate::do_login(transport.clone(), &mut account)?;
|
||||||
}
|
}
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
error!("Failed to get trade confirmations: {}", err);
|
error!("Failed to get trade confirmations: {}", err);
|
||||||
|
@ -65,7 +69,7 @@ impl AccountCommand for TradeCommand {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
let confirmer = Confirmer::new(&account);
|
let confirmer = Confirmer::new(transport.clone(), &account);
|
||||||
let mut any_failed = false;
|
let mut any_failed = false;
|
||||||
if self.accept_all {
|
if self.accept_all {
|
||||||
info!("accepting all confirmations");
|
info!("accepting all confirmations");
|
||||||
|
|
41
src/main.rs
41
src/main.rs
|
@ -10,7 +10,7 @@ use std::{
|
||||||
use steamguard::{
|
use steamguard::{
|
||||||
protobufs::steammessages_auth_steamclient::{EAuthSessionGuardType, EAuthTokenPlatformType},
|
protobufs::steammessages_auth_steamclient::{EAuthSessionGuardType, EAuthTokenPlatformType},
|
||||||
refresher::TokenRefresher,
|
refresher::TokenRefresher,
|
||||||
transport::WebApiTransport,
|
transport::{Transport, WebApiTransport},
|
||||||
};
|
};
|
||||||
use steamguard::{steamapi, DeviceDetails, LoginError, SteamGuardAccount, UserLogin};
|
use steamguard::{steamapi, DeviceDetails, LoginError, SteamGuardAccount, UserLogin};
|
||||||
use steamguard::{steamapi::AuthenticationClient, token::Tokens};
|
use steamguard::{steamapi::AuthenticationClient, token::Tokens};
|
||||||
|
@ -83,7 +83,7 @@ fn main() {
|
||||||
fn run(args: commands::Args) -> anyhow::Result<()> {
|
fn run(args: commands::Args) -> anyhow::Result<()> {
|
||||||
let globalargs = args.global;
|
let globalargs = args.global;
|
||||||
|
|
||||||
let cmd: CommandType = match args.sub.unwrap_or(Subcommands::Code(args.code)) {
|
let cmd: CommandType<WebApiTransport> = match args.sub.unwrap_or(Subcommands::Code(args.code)) {
|
||||||
Subcommands::Debug(args) => CommandType::Const(Box::new(args)),
|
Subcommands::Debug(args) => CommandType::Const(Box::new(args)),
|
||||||
Subcommands::Completion(args) => CommandType::Const(Box::new(args)),
|
Subcommands::Completion(args) => CommandType::Const(Box::new(args)),
|
||||||
Subcommands::Setup(args) => CommandType::Manifest(Box::new(args)),
|
Subcommands::Setup(args) => CommandType::Manifest(Box::new(args)),
|
||||||
|
@ -198,8 +198,16 @@ fn run(args: commands::Args) -> anyhow::Result<()> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let mut http_client = reqwest::blocking::Client::builder();
|
||||||
|
if let Some(proxy) = &globalargs.http_proxy {
|
||||||
|
let proxy = reqwest::Proxy::all(proxy)?;
|
||||||
|
http_client = http_client.proxy(proxy);
|
||||||
|
}
|
||||||
|
let http_client = http_client.build()?;
|
||||||
|
let transport = WebApiTransport::new(http_client);
|
||||||
|
|
||||||
if let CommandType::Manifest(cmd) = cmd {
|
if let CommandType::Manifest(cmd) = cmd {
|
||||||
cmd.execute(&mut manager)?;
|
cmd.execute(transport, &mut manager)?;
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -237,7 +245,7 @@ fn run(args: commands::Args) -> anyhow::Result<()> {
|
||||||
);
|
);
|
||||||
|
|
||||||
if let CommandType::Account(cmd) = cmd {
|
if let CommandType::Account(cmd) = cmd {
|
||||||
return cmd.execute(&mut manager, selected_accounts);
|
return cmd.execute(transport, &mut manager, selected_accounts);
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
@ -271,10 +279,13 @@ fn get_selected_accounts(
|
||||||
Ok(selected_accounts)
|
Ok(selected_accounts)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn do_login(account: &mut SteamGuardAccount) -> anyhow::Result<()> {
|
fn do_login<T: Transport + Clone>(
|
||||||
|
transport: T,
|
||||||
|
account: &mut SteamGuardAccount,
|
||||||
|
) -> anyhow::Result<()> {
|
||||||
if let Some(tokens) = account.tokens.as_mut() {
|
if let Some(tokens) = account.tokens.as_mut() {
|
||||||
info!("Refreshing access token...");
|
info!("Refreshing access token...");
|
||||||
let client = AuthenticationClient::new(WebApiTransport::default());
|
let client = AuthenticationClient::new(transport.clone());
|
||||||
let mut refresher = TokenRefresher::new(client);
|
let mut refresher = TokenRefresher::new(client);
|
||||||
match refresher.refresh(account.steam_id, tokens) {
|
match refresher.refresh(account.steam_id, tokens) {
|
||||||
Ok(token) => {
|
Ok(token) => {
|
||||||
|
@ -304,14 +315,19 @@ fn do_login(account: &mut SteamGuardAccount) -> anyhow::Result<()> {
|
||||||
} else {
|
} else {
|
||||||
debug!("password is empty");
|
debug!("password is empty");
|
||||||
}
|
}
|
||||||
let tokens = do_login_impl(account.account_name.clone(), password, Some(account))?;
|
let tokens = do_login_impl(
|
||||||
|
transport,
|
||||||
|
account.account_name.clone(),
|
||||||
|
password,
|
||||||
|
Some(account),
|
||||||
|
)?;
|
||||||
let steam_id = tokens.access_token().decode()?.steam_id();
|
let steam_id = tokens.access_token().decode()?.steam_id();
|
||||||
account.set_tokens(tokens);
|
account.set_tokens(tokens);
|
||||||
account.steam_id = steam_id;
|
account.steam_id = steam_id;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn do_login_raw(username: String) -> anyhow::Result<Tokens> {
|
fn do_login_raw<T: Transport + Clone>(transport: T, username: String) -> anyhow::Result<Tokens> {
|
||||||
let _ = std::io::stdout().flush();
|
let _ = std::io::stdout().flush();
|
||||||
let password = rpassword::prompt_password_stdout("Password: ").unwrap();
|
let password = rpassword::prompt_password_stdout("Password: ").unwrap();
|
||||||
if !password.is_empty() {
|
if !password.is_empty() {
|
||||||
|
@ -319,15 +335,16 @@ fn do_login_raw(username: String) -> anyhow::Result<Tokens> {
|
||||||
} else {
|
} else {
|
||||||
debug!("password is empty");
|
debug!("password is empty");
|
||||||
}
|
}
|
||||||
do_login_impl(username, password, None)
|
do_login_impl(transport, username, password, None)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn do_login_impl(
|
fn do_login_impl<T: Transport + Clone>(
|
||||||
|
transport: T,
|
||||||
username: String,
|
username: String,
|
||||||
password: String,
|
password: String,
|
||||||
account: Option<&SteamGuardAccount>,
|
account: Option<&SteamGuardAccount>,
|
||||||
) -> anyhow::Result<Tokens> {
|
) -> anyhow::Result<Tokens> {
|
||||||
let mut login = UserLogin::new(WebApiTransport::default(), build_device_details());
|
let mut login = UserLogin::new(transport.clone(), build_device_details());
|
||||||
|
|
||||||
let mut password = password;
|
let mut password = password;
|
||||||
let confirmation_methods;
|
let confirmation_methods;
|
||||||
|
@ -375,7 +392,7 @@ fn do_login_impl(
|
||||||
EAuthSessionGuardType::k_EAuthSessionGuardType_DeviceCode => {
|
EAuthSessionGuardType::k_EAuthSessionGuardType_DeviceCode => {
|
||||||
let code = if let Some(account) = account {
|
let code = if let Some(account) = account {
|
||||||
debug!("Generating 2fa code...");
|
debug!("Generating 2fa code...");
|
||||||
let time = steamapi::get_server_time()?.server_time();
|
let time = steamapi::get_server_time(transport)?.server_time();
|
||||||
account.generate_code(time)
|
account.generate_code(time)
|
||||||
} else {
|
} else {
|
||||||
eprint!("Enter the 2fa code from your device: ");
|
eprint!("Enter the 2fa code from your device: ");
|
||||||
|
|
|
@ -10,20 +10,30 @@ use reqwest::{
|
||||||
use secrecy::ExposeSecret;
|
use secrecy::ExposeSecret;
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
|
|
||||||
use crate::{steamapi, SteamGuardAccount};
|
use crate::{
|
||||||
|
steamapi::{self},
|
||||||
|
transport::Transport,
|
||||||
|
SteamGuardAccount,
|
||||||
|
};
|
||||||
|
|
||||||
lazy_static! {
|
lazy_static! {
|
||||||
static ref STEAM_COOKIE_URL: Url = "https://steamcommunity.com".parse::<Url>().unwrap();
|
static ref STEAM_COOKIE_URL: Url = "https://steamcommunity.com".parse::<Url>().unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Provides an interface that wraps the Steam mobile confirmation API.
|
/// Provides an interface that wraps the Steam mobile confirmation API.
|
||||||
pub struct Confirmer<'a> {
|
///
|
||||||
|
/// Only compatible with WebApiTransport.
|
||||||
|
pub struct Confirmer<'a, T> {
|
||||||
account: &'a SteamGuardAccount,
|
account: &'a SteamGuardAccount,
|
||||||
|
transport: T,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> Confirmer<'a> {
|
impl<'a, T> Confirmer<'a, T>
|
||||||
pub fn new(account: &'a SteamGuardAccount) -> Self {
|
where
|
||||||
Self { account }
|
T: Transport + Clone,
|
||||||
|
{
|
||||||
|
pub fn new(transport: T, account: &'a SteamGuardAccount) -> Self {
|
||||||
|
Self { account, transport }
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_confirmation_query_params<'q>(
|
fn get_confirmation_query_params<'q>(
|
||||||
|
@ -72,11 +82,9 @@ impl<'a> Confirmer<'a> {
|
||||||
|
|
||||||
pub fn get_trade_confirmations(&self) -> Result<Vec<Confirmation>, ConfirmerError> {
|
pub fn get_trade_confirmations(&self) -> Result<Vec<Confirmation>, ConfirmerError> {
|
||||||
let cookies = self.build_cookie_jar();
|
let cookies = self.build_cookie_jar();
|
||||||
let client = reqwest::blocking::ClientBuilder::new()
|
let client = self.transport.innner_http_client()?;
|
||||||
.cookie_store(true)
|
|
||||||
.build()?;
|
|
||||||
|
|
||||||
let time = steamapi::get_server_time()?.server_time();
|
let time = steamapi::get_server_time(self.transport.clone())?.server_time();
|
||||||
let resp = client
|
let resp = client
|
||||||
.get(
|
.get(
|
||||||
"https://steamcommunity.com/mobileconf/getlist"
|
"https://steamcommunity.com/mobileconf/getlist"
|
||||||
|
@ -117,11 +125,9 @@ impl<'a> Confirmer<'a> {
|
||||||
let operation = action.to_operation();
|
let operation = action.to_operation();
|
||||||
|
|
||||||
let cookies = self.build_cookie_jar();
|
let cookies = self.build_cookie_jar();
|
||||||
let client = reqwest::blocking::ClientBuilder::new()
|
let client = self.transport.innner_http_client()?;
|
||||||
.cookie_store(true)
|
|
||||||
.build()?;
|
|
||||||
|
|
||||||
let time = steamapi::get_server_time()?.server_time();
|
let time = steamapi::get_server_time(self.transport.clone())?.server_time();
|
||||||
let mut query_params = self.get_confirmation_query_params("conf", time);
|
let mut query_params = self.get_confirmation_query_params("conf", time);
|
||||||
query_params.push(("op", operation.into()));
|
query_params.push(("op", operation.into()));
|
||||||
query_params.push(("cid", Cow::Borrowed(&conf.id)));
|
query_params.push(("cid", Cow::Borrowed(&conf.id)));
|
||||||
|
@ -185,11 +191,9 @@ impl<'a> Confirmer<'a> {
|
||||||
let operation = action.to_operation();
|
let operation = action.to_operation();
|
||||||
|
|
||||||
let cookies = self.build_cookie_jar();
|
let cookies = self.build_cookie_jar();
|
||||||
let client = reqwest::blocking::ClientBuilder::new()
|
let client = self.transport.innner_http_client()?;
|
||||||
.cookie_store(true)
|
|
||||||
.build()?;
|
|
||||||
|
|
||||||
let time = steamapi::get_server_time()?.server_time();
|
let time = steamapi::get_server_time(self.transport.clone())?.server_time();
|
||||||
let mut query_params = self.get_confirmation_query_params("conf", time);
|
let mut query_params = self.get_confirmation_query_params("conf", time);
|
||||||
query_params.push(("op", operation.into()));
|
query_params.push(("op", operation.into()));
|
||||||
for conf in confs.iter() {
|
for conf in confs.iter() {
|
||||||
|
@ -262,11 +266,9 @@ impl<'a> Confirmer<'a> {
|
||||||
}
|
}
|
||||||
|
|
||||||
let cookies = self.build_cookie_jar();
|
let cookies = self.build_cookie_jar();
|
||||||
let client = reqwest::blocking::ClientBuilder::new()
|
let client = self.transport.innner_http_client()?;
|
||||||
.cookie_store(true)
|
|
||||||
.build()?;
|
|
||||||
|
|
||||||
let time = steamapi::get_server_time()?.server_time();
|
let time = steamapi::get_server_time(self.transport.clone())?.server_time();
|
||||||
let query_params = self.get_confirmation_query_params("details", time);
|
let query_params = self.get_confirmation_query_params("details", time);
|
||||||
|
|
||||||
let resp = client
|
let resp = client
|
||||||
|
|
|
@ -1,8 +1,6 @@
|
||||||
use crate::protobufs::service_twofactor::CTwoFactor_RemoveAuthenticator_Request;
|
use crate::protobufs::service_twofactor::CTwoFactor_RemoveAuthenticator_Request;
|
||||||
use crate::steamapi::EResult;
|
use crate::steamapi::EResult;
|
||||||
use crate::{
|
use crate::{steamapi::twofactor::TwoFactorClient, token::TwoFactorSecret};
|
||||||
steamapi::twofactor::TwoFactorClient, token::TwoFactorSecret, transport::WebApiTransport,
|
|
||||||
};
|
|
||||||
pub use accountlinker::{AccountLinkError, AccountLinker, FinalizeLinkError};
|
pub use accountlinker::{AccountLinkError, AccountLinker, FinalizeLinkError};
|
||||||
pub use confirmation::*;
|
pub use confirmation::*;
|
||||||
pub use qrapprover::{QrApprover, QrApproverError};
|
pub use qrapprover::{QrApprover, QrApproverError};
|
||||||
|
@ -10,7 +8,7 @@ pub use secrecy::{ExposeSecret, SecretString};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use std::io::Read;
|
use std::io::Read;
|
||||||
use token::Tokens;
|
use token::Tokens;
|
||||||
use transport::TransportError;
|
use transport::{Transport, TransportError};
|
||||||
pub use userlogin::{DeviceDetails, LoginError, UserLogin};
|
pub use userlogin::{DeviceDetails, LoginError, UserLogin};
|
||||||
|
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
|
@ -99,8 +97,9 @@ impl SteamGuardAccount {
|
||||||
|
|
||||||
/// Removes the mobile authenticator from the steam account. If this operation succeeds, this object can no longer be considered valid.
|
/// Removes the mobile authenticator from the steam account. If this operation succeeds, this object can no longer be considered valid.
|
||||||
/// Returns whether or not the operation was successful.
|
/// Returns whether or not the operation was successful.
|
||||||
pub fn remove_authenticator(
|
pub fn remove_authenticator<T: Transport>(
|
||||||
&self,
|
&self,
|
||||||
|
client: &TwoFactorClient<T>,
|
||||||
revocation_code: Option<&String>,
|
revocation_code: Option<&String>,
|
||||||
) -> Result<(), RemoveAuthenticatorError> {
|
) -> Result<(), RemoveAuthenticatorError> {
|
||||||
if !matches!(revocation_code, Some(_)) && self.revocation_code.expose_secret().is_empty() {
|
if !matches!(revocation_code, Some(_)) && self.revocation_code.expose_secret().is_empty() {
|
||||||
|
@ -109,7 +108,6 @@ impl SteamGuardAccount {
|
||||||
let Some(tokens) = &self.tokens else {
|
let Some(tokens) = &self.tokens else {
|
||||||
return Err(RemoveAuthenticatorError::TransportError(TransportError::Unauthorized));
|
return Err(RemoveAuthenticatorError::TransportError(TransportError::Unauthorized));
|
||||||
};
|
};
|
||||||
let mut client = TwoFactorClient::new(WebApiTransport::default());
|
|
||||||
let mut req = CTwoFactor_RemoveAuthenticator_Request::new();
|
let mut req = CTwoFactor_RemoveAuthenticator_Request::new();
|
||||||
req.set_revocation_code(
|
req.set_revocation_code(
|
||||||
revocation_code
|
revocation_code
|
||||||
|
|
|
@ -2,9 +2,8 @@ pub mod authentication;
|
||||||
pub mod phone;
|
pub mod phone;
|
||||||
pub mod twofactor;
|
pub mod twofactor;
|
||||||
|
|
||||||
use crate::{
|
use crate::transport::Transport;
|
||||||
protobufs::service_twofactor::CTwoFactor_Time_Response, token::Jwt, transport::WebApiTransport,
|
use crate::{protobufs::service_twofactor::CTwoFactor_Time_Response, token::Jwt};
|
||||||
};
|
|
||||||
use reqwest::Url;
|
use reqwest::Url;
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
|
|
||||||
|
@ -20,8 +19,8 @@ lazy_static! {
|
||||||
/// Queries Steam for the current time. A convenience function around TwoFactorClient.
|
/// Queries Steam for the current time. A convenience function around TwoFactorClient.
|
||||||
///
|
///
|
||||||
/// Endpoint: `/ITwoFactorService/QueryTime/v0001`
|
/// Endpoint: `/ITwoFactorService/QueryTime/v0001`
|
||||||
pub fn get_server_time() -> anyhow::Result<CTwoFactor_Time_Response> {
|
pub fn get_server_time<T: Transport>(client: T) -> anyhow::Result<CTwoFactor_Time_Response> {
|
||||||
let mut client = TwoFactorClient::new(WebApiTransport::default());
|
let client = TwoFactorClient::new(client);
|
||||||
let resp = client.query_time()?;
|
let resp = client.query_time()?;
|
||||||
if resp.result != EResult::OK {
|
if resp.result != EResult::OK {
|
||||||
return Err(anyhow::anyhow!("QueryTime failed: {:?}", resp));
|
return Err(anyhow::anyhow!("QueryTime failed: {:?}", resp));
|
||||||
|
|
|
@ -26,7 +26,7 @@ where
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn add_authenticator(
|
pub fn add_authenticator(
|
||||||
&mut self,
|
&self,
|
||||||
req: CTwoFactor_AddAuthenticator_Request,
|
req: CTwoFactor_AddAuthenticator_Request,
|
||||||
access_token: &Jwt,
|
access_token: &Jwt,
|
||||||
) -> anyhow::Result<ApiResponse<CTwoFactor_AddAuthenticator_Response>> {
|
) -> anyhow::Result<ApiResponse<CTwoFactor_AddAuthenticator_Response>> {
|
||||||
|
@ -41,7 +41,7 @@ where
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn finalize_authenticator(
|
pub fn finalize_authenticator(
|
||||||
&mut self,
|
&self,
|
||||||
req: CTwoFactor_FinalizeAddAuthenticator_Request,
|
req: CTwoFactor_FinalizeAddAuthenticator_Request,
|
||||||
access_token: &Jwt,
|
access_token: &Jwt,
|
||||||
) -> anyhow::Result<ApiResponse<CTwoFactor_FinalizeAddAuthenticator_Response>> {
|
) -> anyhow::Result<ApiResponse<CTwoFactor_FinalizeAddAuthenticator_Response>> {
|
||||||
|
@ -56,7 +56,7 @@ where
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn remove_authenticator(
|
pub fn remove_authenticator(
|
||||||
&mut self,
|
&self,
|
||||||
req: CTwoFactor_RemoveAuthenticator_Request,
|
req: CTwoFactor_RemoveAuthenticator_Request,
|
||||||
access_token: &Jwt,
|
access_token: &Jwt,
|
||||||
) -> Result<ApiResponse<CTwoFactor_RemoveAuthenticator_Response>, TransportError> {
|
) -> Result<ApiResponse<CTwoFactor_RemoveAuthenticator_Response>, TransportError> {
|
||||||
|
@ -71,7 +71,7 @@ where
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn query_status(
|
pub fn query_status(
|
||||||
&mut self,
|
&self,
|
||||||
req: CTwoFactor_Status_Request,
|
req: CTwoFactor_Status_Request,
|
||||||
access_token: &Jwt,
|
access_token: &Jwt,
|
||||||
) -> anyhow::Result<ApiResponse<CTwoFactor_Status_Response>> {
|
) -> anyhow::Result<ApiResponse<CTwoFactor_Status_Response>> {
|
||||||
|
@ -83,7 +83,7 @@ where
|
||||||
Ok(resp)
|
Ok(resp)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn query_time(&mut self) -> anyhow::Result<ApiResponse<CTwoFactor_Time_Response>> {
|
pub fn query_time(&self) -> anyhow::Result<ApiResponse<CTwoFactor_Time_Response>> {
|
||||||
let req = ApiRequest::new(SERVICE_NAME, "QueryTime", 1, CTwoFactor_Time_Request::new());
|
let req = ApiRequest::new(SERVICE_NAME, "QueryTime", 1, CTwoFactor_Time_Request::new());
|
||||||
let resp = self
|
let resp = self
|
||||||
.transport
|
.transport
|
||||||
|
|
|
@ -12,6 +12,10 @@ pub trait Transport {
|
||||||
) -> Result<ApiResponse<Res>, TransportError>;
|
) -> Result<ApiResponse<Res>, TransportError>;
|
||||||
|
|
||||||
fn close(&mut self);
|
fn close(&mut self);
|
||||||
|
|
||||||
|
fn innner_http_client(&self) -> anyhow::Result<reqwest::blocking::Client> {
|
||||||
|
bail!("Transport does not support extracting HTTP client")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, thiserror::Error)]
|
#[derive(Debug, thiserror::Error)]
|
||||||
|
|
|
@ -10,31 +10,15 @@ lazy_static! {
|
||||||
static ref STEAM_API_BASE: String = "https://api.steampowered.com".into();
|
static ref STEAM_API_BASE: String = "https://api.steampowered.com".into();
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct WebApiTransport {
|
pub struct WebApiTransport {
|
||||||
client: reqwest::blocking::Client,
|
client: reqwest::blocking::Client,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for WebApiTransport {
|
|
||||||
fn default() -> Self {
|
|
||||||
Self::new(reqwest::blocking::Client::new())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl WebApiTransport {
|
impl WebApiTransport {
|
||||||
pub fn new(client: reqwest::blocking::Client) -> Self {
|
pub fn new(client: reqwest::blocking::Client) -> Self {
|
||||||
Self { client }
|
Self { client }
|
||||||
}
|
}
|
||||||
|
|
||||||
// pub fn new_with_proxy(proxy: &str) -> Self {
|
|
||||||
// Self {
|
|
||||||
// client: reqwest::blocking::Client::builder()
|
|
||||||
// // .danger_accept_invalid_certs(true)
|
|
||||||
// .proxy(reqwest::Proxy::all(proxy).unwrap())
|
|
||||||
// .build()
|
|
||||||
// .unwrap(),
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Transport for WebApiTransport {
|
impl Transport for WebApiTransport {
|
||||||
|
@ -129,6 +113,10 @@ impl Transport for WebApiTransport {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn close(&mut self) {}
|
fn close(&mut self) {}
|
||||||
|
|
||||||
|
fn innner_http_client(&self) -> anyhow::Result<reqwest::blocking::Client> {
|
||||||
|
Ok(self.client.clone())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn encode_msg<T: MessageFull>(msg: &T, config: base64::Config) -> anyhow::Result<String> {
|
fn encode_msg<T: MessageFull>(msg: &T, config: base64::Config) -> anyhow::Result<String> {
|
||||||
|
|
Loading…
Reference in a new issue