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::{
steamapi, AccountLinker, Confirmation, ConfirmationType, LoginError, SteamGuardAccount,
UserLogin,
UserLogin, FinalizeLinkError
};
use termion::{
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);
println!(
"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;
}
}
let mut account = manifest
.accounts
.last()
.as_ref()
.unwrap()
.clone()
.lock()
.unwrap();
debug!("attempting link finalization");
print!("Enter SMS code: ");
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;
}

View file

@ -2,6 +2,7 @@ use crate::{
steamapi::{AddAuthenticatorResponse, Session, SteamApiClient},
SteamGuardAccount,
};
use log::*;
use std::error::Error;
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()?;
if has_phone && !self.phone_number.is_empty() {
return Err(AccountLinkError::MustRemovePhoneNumber);
bail!(AccountLinkError::MustRemovePhoneNumber);
}
if !has_phone && self.phone_number.is_empty() {
return Err(AccountLinkError::MustProvidePhoneNumber);
bail!(AccountLinkError::MustProvidePhoneNumber);
}
if !has_phone {
if self.sent_confirmation_email {
if !self.client.check_email_confirmation()? {
return Err(AccountLinkError::Unknown(anyhow!(
"Failed email confirmation check"
)));
bail!("Failed email confirmation check");
}
} else if !self.client.add_phone_number(self.phone_number.clone())? {
return Err(AccountLinkError::Unknown(anyhow!(
"Failed to add phone number"
)));
bail!("Failed to add phone number");
} else {
self.sent_confirmation_email = true;
return Err(AccountLinkError::MustConfirmEmail);
bail!(AccountLinkError::MustConfirmEmail);
}
}
@ -61,7 +60,7 @@ impl AccountLinker {
match resp.status {
29 => {
return Err(AccountLinkError::AuthenticatorPresent);
bail!(AccountLinkError::AuthenticatorPresent);
}
1 => {
let mut account = resp.to_steam_guard_account();
@ -70,15 +69,46 @@ impl AccountLinker {
return Ok(account);
}
status => {
return Err(AccountLinkError::Unknown(anyhow!(
"Unknown add authenticator status code: {}",
status
)));
bail!("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 {
@ -96,8 +126,6 @@ pub enum AccountLinkError {
/// Must provide an SMS code
AwaitingFinalization,
AuthenticatorPresent,
NetworkFailure(reqwest::Error),
Unknown(anyhow::Error),
}
impl Display for AccountLinkError {
@ -108,14 +136,17 @@ impl Display for AccountLinkError {
impl Error for AccountLinkError {}
impl From<reqwest::Error> for AccountLinkError {
fn from(err: reqwest::Error) -> AccountLinkError {
AccountLinkError::NetworkFailure(err)
#[derive(Debug)]
pub enum FinalizeLinkError {
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 {
fn from(err: anyhow::Error) -> AccountLinkError {
AccountLinkError::Unknown(err)
}
}
impl Error for FinalizeLinkError {}

View file

@ -118,7 +118,7 @@ pub fn get_server_time() -> i64 {
.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)]
pub struct SteamApiClient {
cookies: reqwest::cookie::Jar,
@ -382,7 +382,7 @@ impl SteamApiClient {
&self,
sms_code: String,
code_2fa: String,
time_2fa: u64,
time_2fa: i64,
) -> anyhow::Result<FinalizeAddAuthenticatorResponse> {
ensure!(matches!(self.session, Some(_)));
let params = hashmap! {
@ -447,7 +447,7 @@ fn test_login_response_parse() {
#[derive(Debug, Clone, Deserialize)]
pub struct SteamApiResponse<T> {
pub response: T
pub response: T,
}
#[derive(Debug, Clone, Deserialize)]
@ -494,4 +494,9 @@ impl AddAuthenticatorResponse {
}
#[derive(Debug, Clone, Deserialize)]
pub struct FinalizeAddAuthenticatorResponse {}
pub struct FinalizeAddAuthenticatorResponse {
pub status: i32,
pub server_time: u64,
pub want_more: bool,
pub success: bool,
}