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::{
|
||||
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;
|
||||
}
|
||||
|
|
|
@ -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 {}
|
||||
|
|
|
@ -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,
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue