diff --git a/src/login.rs b/src/login.rs new file mode 100644 index 0000000..f3e0eb9 --- /dev/null +++ b/src/login.rs @@ -0,0 +1,171 @@ +use std::io::Write; + +use log::*; +use steamguard::{ + protobufs::steammessages_auth_steamclient::{EAuthSessionGuardType, EAuthTokenPlatformType}, + refresher::TokenRefresher, + steamapi::{self, AuthenticationClient}, + token::Tokens, + transport::Transport, + DeviceDetails, LoginError, SteamGuardAccount, UserLogin, +}; + +use crate::tui; + +pub fn do_login( + transport: T, + account: &mut SteamGuardAccount, +) -> anyhow::Result<()> { + if let Some(tokens) = account.tokens.as_mut() { + info!("Refreshing access token..."); + let client = AuthenticationClient::new(transport.clone()); + let mut refresher = TokenRefresher::new(client); + match refresher.refresh(account.steam_id, tokens) { + Ok(token) => { + info!("Successfully refreshed access token, no need to prompt to log in."); + tokens.set_access_token(token); + return Ok(()); + } + Err(err) => { + warn!( + "Failed to refresh access token, prompting for login: {}", + err + ); + } + } + } + + if !account.account_name.is_empty() { + info!("Username: {}", account.account_name); + } else { + eprint!("Username: "); + account.account_name = tui::prompt(); + } + let _ = std::io::stdout().flush(); + let password = rpassword::prompt_password_stdout("Password: ").unwrap(); + if !password.is_empty() { + debug!("password is present"); + } else { + debug!("password is empty"); + } + let tokens = do_login_impl( + transport, + account.account_name.clone(), + password, + Some(account), + )?; + let steam_id = tokens.access_token().decode()?.steam_id(); + account.set_tokens(tokens); + account.steam_id = steam_id; + Ok(()) +} + +pub fn do_login_raw( + transport: T, + username: String, +) -> anyhow::Result { + let _ = std::io::stdout().flush(); + let password = rpassword::prompt_password_stdout("Password: ").unwrap(); + if !password.is_empty() { + debug!("password is present"); + } else { + debug!("password is empty"); + } + do_login_impl(transport, username, password, None) +} + +fn do_login_impl( + transport: T, + username: String, + password: String, + account: Option<&SteamGuardAccount>, +) -> anyhow::Result { + let mut login = UserLogin::new(transport.clone(), build_device_details()); + + let mut password = password; + let confirmation_methods; + loop { + match login.begin_auth_via_credentials(&username, &password) { + Ok(methods) => { + confirmation_methods = methods; + break; + } + Err(LoginError::TooManyAttempts) => { + error!("Too many login attempts. Steam is rate limiting you. Please wait a while and try again later."); + return Err(LoginError::TooManyAttempts.into()); + } + Err(LoginError::BadCredentials) => { + error!("Incorrect password."); + password = rpassword::prompt_password_stdout("Password: ") + .unwrap() + .trim() + .to_owned(); + continue; + } + Err(err) => { + error!("Unexpected error when trying to log in. If you report this as a bug, please rerun with `-v debug` or `-v trace` and include all output in your issue. {:?}", err); + return Err(err.into()); + } + } + } + + for method in confirmation_methods { + match method.confirmation_type { + EAuthSessionGuardType::k_EAuthSessionGuardType_DeviceConfirmation => { + eprintln!("Please confirm this login on your other device."); + eprintln!("Press enter when you have confirmed."); + tui::pause(); + } + EAuthSessionGuardType::k_EAuthSessionGuardType_EmailConfirmation => { + eprint!("Please confirm this login by clicking the link in your email."); + if !method.associated_messsage.is_empty() { + eprint!(" ({})", method.associated_messsage); + } + eprintln!(); + eprintln!("Press enter when you have confirmed."); + tui::pause(); + } + EAuthSessionGuardType::k_EAuthSessionGuardType_DeviceCode => { + let code = if let Some(account) = account { + debug!("Generating 2fa code..."); + let time = steamapi::get_server_time(transport)?.server_time(); + account.generate_code(time) + } else { + eprint!("Enter the 2fa code from your device: "); + tui::prompt().trim().to_owned() + }; + login.submit_steam_guard_code(method.confirmation_type, code)?; + } + EAuthSessionGuardType::k_EAuthSessionGuardType_EmailCode => { + eprint!("Enter the 2fa code sent to your email: "); + let code = tui::prompt().trim().to_owned(); + login.submit_steam_guard_code(method.confirmation_type, code)?; + } + _ => { + warn!("Unknown confirmation method: {:?}", method); + continue; + } + } + break; + } + + info!("Polling for tokens... -- If this takes a long time, try logging in again."); + let tokens = login.poll_until_tokens()?; + + info!("Logged in successfully!"); + Ok(tokens) +} + +fn build_device_details() -> DeviceDetails { + DeviceDetails { + friendly_name: format!( + "{} (steamguard-cli)", + gethostname::gethostname() + .into_string() + .expect("failed to get hostname") + ), + platform_type: EAuthTokenPlatformType::k_EAuthTokenPlatformType_MobileApp, + os_type: -500, + gaming_device_type: 528, + } +} diff --git a/src/main.rs b/src/main.rs index eb9c40e..9b29619 100644 --- a/src/main.rs +++ b/src/main.rs @@ -3,21 +3,16 @@ use clap::Parser; use log::*; use secrecy::SecretString; use std::{ - io::Write, path::Path, sync::{Arc, Mutex}, }; -use steamguard::{ - protobufs::steammessages_auth_steamclient::{EAuthSessionGuardType, EAuthTokenPlatformType}, - refresher::TokenRefresher, - transport::{Transport, WebApiTransport}, -}; -use steamguard::{steamapi, DeviceDetails, LoginError, SteamGuardAccount, UserLogin}; -use steamguard::{steamapi::AuthenticationClient, token::Tokens}; +use steamguard::transport::WebApiTransport; +use steamguard::SteamGuardAccount; use crate::accountmanager::migrate::{load_and_migrate, MigrationError}; pub use crate::accountmanager::{AccountManager, ManifestAccountLoadError, ManifestLoadError}; use crate::commands::{CommandType, Subcommands}; +pub use login::*; extern crate lazy_static; #[macro_use] @@ -32,8 +27,10 @@ mod commands; mod debug; mod encryption; mod errors; +mod login; mod secret_string; pub(crate) mod tui; + #[cfg(feature = "updater")] mod updater; @@ -304,161 +301,6 @@ fn get_selected_accounts( Ok(selected_accounts) } -fn do_login( - transport: T, - account: &mut SteamGuardAccount, -) -> anyhow::Result<()> { - if let Some(tokens) = account.tokens.as_mut() { - info!("Refreshing access token..."); - let client = AuthenticationClient::new(transport.clone()); - let mut refresher = TokenRefresher::new(client); - match refresher.refresh(account.steam_id, tokens) { - Ok(token) => { - info!("Successfully refreshed access token, no need to prompt to log in."); - tokens.set_access_token(token); - return Ok(()); - } - Err(err) => { - warn!( - "Failed to refresh access token, prompting for login: {}", - err - ); - } - } - } - - if !account.account_name.is_empty() { - info!("Username: {}", account.account_name); - } else { - eprint!("Username: "); - account.account_name = tui::prompt(); - } - let _ = std::io::stdout().flush(); - let password = rpassword::prompt_password_stdout("Password: ").unwrap(); - if !password.is_empty() { - debug!("password is present"); - } else { - debug!("password is empty"); - } - let tokens = do_login_impl( - transport, - account.account_name.clone(), - password, - Some(account), - )?; - let steam_id = tokens.access_token().decode()?.steam_id(); - account.set_tokens(tokens); - account.steam_id = steam_id; - Ok(()) -} - -fn do_login_raw(transport: T, username: String) -> anyhow::Result { - let _ = std::io::stdout().flush(); - let password = rpassword::prompt_password_stdout("Password: ").unwrap(); - if !password.is_empty() { - debug!("password is present"); - } else { - debug!("password is empty"); - } - do_login_impl(transport, username, password, None) -} - -fn do_login_impl( - transport: T, - username: String, - password: String, - account: Option<&SteamGuardAccount>, -) -> anyhow::Result { - let mut login = UserLogin::new(transport.clone(), build_device_details()); - - let mut password = password; - let confirmation_methods; - loop { - match login.begin_auth_via_credentials(&username, &password) { - Ok(methods) => { - confirmation_methods = methods; - break; - } - Err(LoginError::TooManyAttempts) => { - error!("Too many login attempts. Steam is rate limiting you. Please wait a while and try again later."); - return Err(LoginError::TooManyAttempts.into()); - } - Err(LoginError::BadCredentials) => { - error!("Incorrect password."); - password = rpassword::prompt_password_stdout("Password: ") - .unwrap() - .trim() - .to_owned(); - continue; - } - Err(err) => { - error!("Unexpected error when trying to log in. If you report this as a bug, please rerun with `-v debug` or `-v trace` and include all output in your issue. {:?}", err); - return Err(err.into()); - } - } - } - - for method in confirmation_methods { - match method.confirmation_type { - EAuthSessionGuardType::k_EAuthSessionGuardType_DeviceConfirmation => { - eprintln!("Please confirm this login on your other device."); - eprintln!("Press enter when you have confirmed."); - tui::pause(); - } - EAuthSessionGuardType::k_EAuthSessionGuardType_EmailConfirmation => { - eprint!("Please confirm this login by clicking the link in your email."); - if !method.associated_messsage.is_empty() { - eprint!(" ({})", method.associated_messsage); - } - eprintln!(); - eprintln!("Press enter when you have confirmed."); - tui::pause(); - } - EAuthSessionGuardType::k_EAuthSessionGuardType_DeviceCode => { - let code = if let Some(account) = account { - debug!("Generating 2fa code..."); - let time = steamapi::get_server_time(transport)?.server_time(); - account.generate_code(time) - } else { - eprint!("Enter the 2fa code from your device: "); - tui::prompt().trim().to_owned() - }; - login.submit_steam_guard_code(method.confirmation_type, code)?; - } - EAuthSessionGuardType::k_EAuthSessionGuardType_EmailCode => { - eprint!("Enter the 2fa code sent to your email: "); - let code = tui::prompt().trim().to_owned(); - login.submit_steam_guard_code(method.confirmation_type, code)?; - } - _ => { - warn!("Unknown confirmation method: {:?}", method); - continue; - } - } - break; - } - - info!("Polling for tokens... -- If this takes a long time, try logging in again."); - let tokens = login.poll_until_tokens()?; - - info!("Logged in successfully!"); - Ok(tokens) -} - -fn build_device_details() -> DeviceDetails { - DeviceDetails { - friendly_name: format!( - "{} (steamguard-cli)", - gethostname::gethostname() - .into_string() - .expect("failed to get hostname") - ), - platform_type: EAuthTokenPlatformType::k_EAuthTokenPlatformType_MobileApp, - os_type: -500, - gaming_device_type: 528, - } -} - fn get_mafiles_dir() -> String { let paths = vec![ Path::new(&dirs::config_dir().unwrap()).join("steamguard-cli/maFiles"),