add account link finalization
This commit is contained in:
parent
e0578fce39
commit
db2ec59c07
3 changed files with 96 additions and 33 deletions
33
src/main.rs
33
src/main.rs
|
@ -10,7 +10,7 @@ use std::{
|
||||||
};
|
};
|
||||||
use steamguard::{
|
use steamguard::{
|
||||||
steamapi, AccountLinker, Confirmation, ConfirmationType, LoginError, SteamGuardAccount,
|
steamapi, AccountLinker, Confirmation, ConfirmationType, LoginError, SteamGuardAccount,
|
||||||
UserLogin,
|
UserLogin, FinalizeLinkError
|
||||||
};
|
};
|
||||||
use termion::{
|
use termion::{
|
||||||
event::{Event, Key},
|
event::{Event, Key},
|
||||||
|
@ -152,16 +152,43 @@ fn main() {
|
||||||
error!("Aborting the account linking process because we failed to save the manifest. This is really bad. Here is the error: {}", err);
|
error!("Aborting the account linking process because we failed to save the manifest. This is really bad. Here is the error: {}", err);
|
||||||
println!(
|
println!(
|
||||||
"Just in case, here is the account info. Save it somewhere just in case!\n{:?}",
|
"Just in case, here is the account info. Save it somewhere just in case!\n{:?}",
|
||||||
manifest.accounts.last().as_ref().unwrap()
|
manifest.accounts.last().unwrap().lock().unwrap()
|
||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let mut account = manifest
|
||||||
|
.accounts
|
||||||
|
.last()
|
||||||
|
.as_ref()
|
||||||
|
.unwrap()
|
||||||
|
.clone()
|
||||||
|
.lock()
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
debug!("attempting link finalization");
|
debug!("attempting link finalization");
|
||||||
print!("Enter SMS code: ");
|
print!("Enter SMS code: ");
|
||||||
let sms_code = prompt();
|
let sms_code = prompt();
|
||||||
linker.finalize(sms_code);
|
let mut tries = 0;
|
||||||
|
loop {
|
||||||
|
match linker.finalize(&mut account, sms_code) {
|
||||||
|
Ok(_) => break,
|
||||||
|
Err(FinalizeLinkError::WantMore) => {
|
||||||
|
debug!("steam wants more 2fa codes (tries: {})", tries);
|
||||||
|
tries += 1;
|
||||||
|
if tries >= 30 {
|
||||||
|
error!("Failed to finalize: unable to generate valid 2fa codes");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
},
|
||||||
|
Err(err) => {
|
||||||
|
error!("Failed to finalize: {}", err);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,6 +2,7 @@ use crate::{
|
||||||
steamapi::{AddAuthenticatorResponse, Session, SteamApiClient},
|
steamapi::{AddAuthenticatorResponse, Session, SteamApiClient},
|
||||||
SteamGuardAccount,
|
SteamGuardAccount,
|
||||||
};
|
};
|
||||||
|
use log::*;
|
||||||
use std::error::Error;
|
use std::error::Error;
|
||||||
use std::fmt::Display;
|
use std::fmt::Display;
|
||||||
|
|
||||||
|
@ -29,30 +30,28 @@ impl AccountLinker {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn link(&mut self) -> anyhow::Result<SteamGuardAccount, AccountLinkError> {
|
pub fn link(&mut self) -> anyhow::Result<SteamGuardAccount> {
|
||||||
|
ensure!(!self.finalized);
|
||||||
|
|
||||||
let has_phone = self.client.has_phone()?;
|
let has_phone = self.client.has_phone()?;
|
||||||
|
|
||||||
if has_phone && !self.phone_number.is_empty() {
|
if has_phone && !self.phone_number.is_empty() {
|
||||||
return Err(AccountLinkError::MustRemovePhoneNumber);
|
bail!(AccountLinkError::MustRemovePhoneNumber);
|
||||||
}
|
}
|
||||||
if !has_phone && self.phone_number.is_empty() {
|
if !has_phone && self.phone_number.is_empty() {
|
||||||
return Err(AccountLinkError::MustProvidePhoneNumber);
|
bail!(AccountLinkError::MustProvidePhoneNumber);
|
||||||
}
|
}
|
||||||
|
|
||||||
if !has_phone {
|
if !has_phone {
|
||||||
if self.sent_confirmation_email {
|
if self.sent_confirmation_email {
|
||||||
if !self.client.check_email_confirmation()? {
|
if !self.client.check_email_confirmation()? {
|
||||||
return Err(AccountLinkError::Unknown(anyhow!(
|
bail!("Failed email confirmation check");
|
||||||
"Failed email confirmation check"
|
|
||||||
)));
|
|
||||||
}
|
}
|
||||||
} else if !self.client.add_phone_number(self.phone_number.clone())? {
|
} else if !self.client.add_phone_number(self.phone_number.clone())? {
|
||||||
return Err(AccountLinkError::Unknown(anyhow!(
|
bail!("Failed to add phone number");
|
||||||
"Failed to add phone number"
|
|
||||||
)));
|
|
||||||
} else {
|
} else {
|
||||||
self.sent_confirmation_email = true;
|
self.sent_confirmation_email = true;
|
||||||
return Err(AccountLinkError::MustConfirmEmail);
|
bail!(AccountLinkError::MustConfirmEmail);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -61,7 +60,7 @@ impl AccountLinker {
|
||||||
|
|
||||||
match resp.status {
|
match resp.status {
|
||||||
29 => {
|
29 => {
|
||||||
return Err(AccountLinkError::AuthenticatorPresent);
|
bail!(AccountLinkError::AuthenticatorPresent);
|
||||||
}
|
}
|
||||||
1 => {
|
1 => {
|
||||||
let mut account = resp.to_steam_guard_account();
|
let mut account = resp.to_steam_guard_account();
|
||||||
|
@ -70,15 +69,46 @@ impl AccountLinker {
|
||||||
return Ok(account);
|
return Ok(account);
|
||||||
}
|
}
|
||||||
status => {
|
status => {
|
||||||
return Err(AccountLinkError::Unknown(anyhow!(
|
bail!("Unknown add authenticator status code: {}", status);
|
||||||
"Unknown add authenticator status code: {}",
|
|
||||||
status
|
|
||||||
)));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn finalize(&self, account: &SteamGuardAccount, sms_code: String) {}
|
/// You may have to call this multiple times. If you have to call it a bunch of times, then you can assume that you are unable to generate correct 2fa codes.
|
||||||
|
pub fn finalize(
|
||||||
|
&mut self,
|
||||||
|
account: &mut SteamGuardAccount,
|
||||||
|
sms_code: String,
|
||||||
|
) -> anyhow::Result<()> {
|
||||||
|
ensure!(!account.fully_enrolled);
|
||||||
|
ensure!(!self.finalized);
|
||||||
|
|
||||||
|
let time = crate::steamapi::get_server_time();
|
||||||
|
let code = account.generate_code(time);
|
||||||
|
let resp = self
|
||||||
|
.client
|
||||||
|
.finalize_authenticator(sms_code.clone(), code, time)?;
|
||||||
|
info!("finalize response status: {}", resp.status);
|
||||||
|
|
||||||
|
match resp.status {
|
||||||
|
89 => {
|
||||||
|
bail!(FinalizeLinkError::BadSmsCode);
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !resp.success {
|
||||||
|
bail!("Failed to finalize authenticator. Status: {}", resp.status);
|
||||||
|
}
|
||||||
|
|
||||||
|
if resp.want_more {
|
||||||
|
bail!(FinalizeLinkError::WantMore);
|
||||||
|
}
|
||||||
|
|
||||||
|
self.finalized = true;
|
||||||
|
account.fully_enrolled = true;
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn generate_device_id() -> String {
|
fn generate_device_id() -> String {
|
||||||
|
@ -96,8 +126,6 @@ pub enum AccountLinkError {
|
||||||
/// Must provide an SMS code
|
/// Must provide an SMS code
|
||||||
AwaitingFinalization,
|
AwaitingFinalization,
|
||||||
AuthenticatorPresent,
|
AuthenticatorPresent,
|
||||||
NetworkFailure(reqwest::Error),
|
|
||||||
Unknown(anyhow::Error),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Display for AccountLinkError {
|
impl Display for AccountLinkError {
|
||||||
|
@ -108,14 +136,17 @@ impl Display for AccountLinkError {
|
||||||
|
|
||||||
impl Error for AccountLinkError {}
|
impl Error for AccountLinkError {}
|
||||||
|
|
||||||
impl From<reqwest::Error> for AccountLinkError {
|
#[derive(Debug)]
|
||||||
fn from(err: reqwest::Error) -> AccountLinkError {
|
pub enum FinalizeLinkError {
|
||||||
AccountLinkError::NetworkFailure(err)
|
BadSmsCode,
|
||||||
|
/// Steam wants more 2fa codes to verify that we can generate valid codes. Call finalize again.
|
||||||
|
WantMore,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for FinalizeLinkError {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter) -> Result<(), std::fmt::Error> {
|
||||||
|
write!(f, "{:?}", self)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<anyhow::Error> for AccountLinkError {
|
impl Error for FinalizeLinkError {}
|
||||||
fn from(err: anyhow::Error) -> AccountLinkError {
|
|
||||||
AccountLinkError::Unknown(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -118,7 +118,7 @@ pub fn get_server_time() -> i64 {
|
||||||
.unwrap();
|
.unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Provides raw access to the Steam API. Handles cookies, some deserialization, etc. to make it easier.
|
/// Provides raw access to the Steam API. Handles cookies, some deserialization, etc. to make it easier. It covers `ITwoFactorService` from the Steam web API, and some mobile app specific api endpoints.
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct SteamApiClient {
|
pub struct SteamApiClient {
|
||||||
cookies: reqwest::cookie::Jar,
|
cookies: reqwest::cookie::Jar,
|
||||||
|
@ -382,7 +382,7 @@ impl SteamApiClient {
|
||||||
&self,
|
&self,
|
||||||
sms_code: String,
|
sms_code: String,
|
||||||
code_2fa: String,
|
code_2fa: String,
|
||||||
time_2fa: u64,
|
time_2fa: i64,
|
||||||
) -> anyhow::Result<FinalizeAddAuthenticatorResponse> {
|
) -> anyhow::Result<FinalizeAddAuthenticatorResponse> {
|
||||||
ensure!(matches!(self.session, Some(_)));
|
ensure!(matches!(self.session, Some(_)));
|
||||||
let params = hashmap! {
|
let params = hashmap! {
|
||||||
|
@ -447,7 +447,7 @@ fn test_login_response_parse() {
|
||||||
|
|
||||||
#[derive(Debug, Clone, Deserialize)]
|
#[derive(Debug, Clone, Deserialize)]
|
||||||
pub struct SteamApiResponse<T> {
|
pub struct SteamApiResponse<T> {
|
||||||
pub response: T
|
pub response: T,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Deserialize)]
|
#[derive(Debug, Clone, Deserialize)]
|
||||||
|
@ -494,4 +494,9 @@ impl AddAuthenticatorResponse {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Deserialize)]
|
#[derive(Debug, Clone, Deserialize)]
|
||||||
pub struct FinalizeAddAuthenticatorResponse {}
|
pub struct FinalizeAddAuthenticatorResponse {
|
||||||
|
pub status: i32,
|
||||||
|
pub server_time: u64,
|
||||||
|
pub want_more: bool,
|
||||||
|
pub success: bool,
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue