172 lines
4.9 KiB
Rust
172 lines
4.9 KiB
Rust
|
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<T: Transport + Clone>(
|
||
|
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<T: Transport + Clone>(
|
||
|
transport: T,
|
||
|
username: String,
|
||
|
) -> anyhow::Result<Tokens> {
|
||
|
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<T: Transport + Clone>(
|
||
|
transport: T,
|
||
|
username: String,
|
||
|
password: String,
|
||
|
account: Option<&SteamGuardAccount>,
|
||
|
) -> anyhow::Result<Tokens> {
|
||
|
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,
|
||
|
}
|
||
|
}
|