add account link finalization

This commit is contained in:
Carson McManus 2021-08-09 00:09:34 -04:00
parent e0578fce39
commit db2ec59c07
3 changed files with 96 additions and 33 deletions

View file

@ -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;
} }

View file

@ -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)
}
}

View file

@ -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,
}