2021-08-08 21:25:27 +02:00
use crate ::{
2021-08-09 00:32:50 +02:00
steamapi ::{ AddAuthenticatorResponse , Session , SteamApiClient } ,
2021-08-08 21:25:27 +02:00
SteamGuardAccount ,
} ;
2021-08-09 06:09:34 +02:00
use log ::* ;
2021-08-05 03:26:14 +02:00
use std ::error ::Error ;
use std ::fmt ::Display ;
2021-07-27 22:24:56 +02:00
2021-08-09 00:32:50 +02:00
#[ derive(Debug) ]
2021-07-27 22:24:56 +02:00
pub struct AccountLinker {
2021-08-08 18:54:46 +02:00
device_id : String ,
phone_number : String ,
2021-08-09 00:32:50 +02:00
pub account : Option < SteamGuardAccount > ,
2021-08-05 03:26:14 +02:00
pub finalized : bool ,
2021-08-09 00:32:50 +02:00
sent_confirmation_email : bool ,
session : Session ,
client : SteamApiClient ,
2021-07-27 22:24:56 +02:00
}
impl AccountLinker {
2021-08-09 00:32:50 +02:00
pub fn new ( session : Session ) -> AccountLinker {
2021-08-08 18:54:46 +02:00
return AccountLinker {
device_id : generate_device_id ( ) ,
2021-08-09 00:32:50 +02:00
phone_number : " " . into ( ) ,
account : None ,
2021-08-05 03:26:14 +02:00
finalized : false ,
2021-08-09 00:32:50 +02:00
sent_confirmation_email : false ,
session : session ,
client : SteamApiClient ::new ( ) ,
2021-08-08 18:54:46 +02:00
} ;
}
2021-07-27 22:24:56 +02:00
2021-08-09 06:09:34 +02:00
pub fn link ( & mut self ) -> anyhow ::Result < SteamGuardAccount > {
ensure! ( ! self . finalized ) ;
2021-08-09 00:32:50 +02:00
let has_phone = self . client . has_phone ( ) ? ;
if has_phone & & ! self . phone_number . is_empty ( ) {
2021-08-09 06:09:34 +02:00
bail! ( AccountLinkError ::MustRemovePhoneNumber ) ;
2021-08-09 00:32:50 +02:00
}
if ! has_phone & & self . phone_number . is_empty ( ) {
2021-08-09 06:09:34 +02:00
bail! ( AccountLinkError ::MustProvidePhoneNumber ) ;
2021-08-09 00:32:50 +02:00
}
if ! has_phone {
if self . sent_confirmation_email {
if ! self . client . check_email_confirmation ( ) ? {
2021-08-09 06:09:34 +02:00
bail! ( " Failed email confirmation check " ) ;
2021-08-09 00:32:50 +02:00
}
} else if ! self . client . add_phone_number ( self . phone_number . clone ( ) ) ? {
2021-08-09 06:09:34 +02:00
bail! ( " Failed to add phone number " ) ;
2021-08-09 00:32:50 +02:00
} else {
self . sent_confirmation_email = true ;
2021-08-09 06:09:34 +02:00
bail! ( AccountLinkError ::MustConfirmEmail ) ;
2021-08-09 00:32:50 +02:00
}
}
2021-08-05 03:26:14 +02:00
2021-08-09 00:32:50 +02:00
let resp : AddAuthenticatorResponse =
self . client . add_authenticator ( self . device_id . clone ( ) ) ? ;
2021-08-05 03:26:14 +02:00
2021-08-09 01:09:15 +02:00
match resp . status {
2021-08-09 00:32:50 +02:00
29 = > {
2021-08-09 06:09:34 +02:00
bail! ( AccountLinkError ::AuthenticatorPresent ) ;
2021-08-09 00:32:50 +02:00
}
1 = > {
let mut account = resp . to_steam_guard_account ( ) ;
account . device_id = self . device_id . clone ( ) ;
account . session = self . client . session . clone ( ) ;
return Ok ( account ) ;
}
status = > {
2021-08-09 06:09:34 +02:00
bail! ( " Unknown add authenticator status code: {} " , status ) ;
2021-08-09 00:32:50 +02:00
}
}
2021-08-08 18:54:46 +02:00
}
2021-07-27 22:24:56 +02:00
2021-08-09 06:09:34 +02:00
/// 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 ( ( ) ) ;
}
2021-07-27 22:24:56 +02:00
}
fn generate_device_id ( ) -> String {
2021-08-08 18:54:46 +02:00
return format! ( " android: {} " , uuid ::Uuid ::new_v4 ( ) . to_string ( ) ) ;
2021-07-27 22:24:56 +02:00
}
2021-08-05 03:26:14 +02:00
#[ derive(Debug) ]
pub enum AccountLinkError {
/// No phone number on the account
MustProvidePhoneNumber ,
/// A phone number is already on the account
MustRemovePhoneNumber ,
/// User need to click link from confirmation email
MustConfirmEmail ,
/// Must provide an SMS code
AwaitingFinalization ,
AuthenticatorPresent ,
}
impl Display for AccountLinkError {
fn fmt ( & self , f : & mut std ::fmt ::Formatter ) -> Result < ( ) , std ::fmt ::Error > {
write! ( f , " {:?} " , self )
}
}
impl Error for AccountLinkError { }
2021-08-09 06:09:34 +02:00
#[ derive(Debug) ]
pub enum FinalizeLinkError {
BadSmsCode ,
/// Steam wants more 2fa codes to verify that we can generate valid codes. Call finalize again.
WantMore ,
2021-08-05 03:26:14 +02:00
}
2021-08-09 00:32:50 +02:00
2021-08-09 06:09:34 +02:00
impl Display for FinalizeLinkError {
fn fmt ( & self , f : & mut std ::fmt ::Formatter ) -> Result < ( ) , std ::fmt ::Error > {
write! ( f , " {:?} " , self )
2021-08-09 00:32:50 +02:00
}
}
2021-08-09 06:09:34 +02:00
impl Error for FinalizeLinkError { }