2021-08-08 21:25:27 +02:00
use crate ::{
2021-08-10 00:44:42 +02:00
steamapi ::{ AddAuthenticatorResponse , Session , SteamApiClient , FinalizeAddAuthenticatorResponse } ,
2021-08-08 21:25:27 +02:00
SteamGuardAccount ,
} ;
2021-08-09 06:09:34 +02:00
use log ::* ;
2021-08-10 00:44:42 +02:00
use thiserror ::Error ;
2021-08-05 03:26:14 +02:00
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-10 00:44:42 +02:00
pub fn link ( & mut self ) -> anyhow ::Result < SteamGuardAccount , AccountLinkError > {
2021-08-09 06:09:34 +02:00
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-10 00:44:42 +02:00
return Err ( AccountLinkError ::MustRemovePhoneNumber ) ;
2021-08-09 00:32:50 +02:00
}
if ! has_phone & & self . phone_number . is_empty ( ) {
2021-08-10 00:44:42 +02:00
return Err ( 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-10 00:44:42 +02:00
return Err ( anyhow! ( " 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-10 00:44:42 +02:00
return Err ( anyhow! ( " Failed to add phone number " ) ) ? ;
2021-08-09 00:32:50 +02:00
} else {
self . sent_confirmation_email = true ;
2021-08-10 00:44:42 +02:00
return Err ( 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-10 00:44:42 +02:00
return Err ( 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-10 00:44:42 +02:00
return Err ( anyhow! ( " 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 ,
2021-08-10 00:44:42 +02:00
) -> anyhow ::Result < ( ) , FinalizeLinkError > {
2021-08-09 06:09:34 +02:00
let time = crate ::steamapi ::get_server_time ( ) ;
let code = account . generate_code ( time ) ;
2021-08-10 00:44:42 +02:00
let resp : FinalizeAddAuthenticatorResponse = self
2021-08-09 06:09:34 +02:00
. client
. finalize_authenticator ( sms_code . clone ( ) , code , time ) ? ;
info! ( " finalize response status: {} " , resp . status ) ;
match resp . status {
89 = > {
2021-08-10 00:44:42 +02:00
return Err ( FinalizeLinkError ::BadSmsCode ) ;
2021-08-09 06:09:34 +02:00
}
_ = > { }
}
if ! resp . success {
2021-08-10 00:44:42 +02:00
return Err ( FinalizeLinkError ::Failure { status : resp . status } ) ? ;
2021-08-09 06:09:34 +02:00
}
if resp . want_more {
2021-08-10 00:44:42 +02:00
return Err ( FinalizeLinkError ::WantMore ) ;
2021-08-09 06:09:34 +02:00
}
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-10 00:44:42 +02:00
#[ derive(Error, Debug) ]
2021-08-05 03:26:14 +02:00
pub enum AccountLinkError {
/// No phone number on the account
2021-08-10 00:44:42 +02:00
#[ error( " A phone number is needed, but not already present on the account. " ) ]
2021-08-05 03:26:14 +02:00
MustProvidePhoneNumber ,
/// A phone number is already on the account
2021-08-10 00:44:42 +02:00
#[ error( " A phone number was provided, but one is already present on the account. " ) ]
2021-08-05 03:26:14 +02:00
MustRemovePhoneNumber ,
/// User need to click link from confirmation email
2021-08-10 00:44:42 +02:00
#[ error( " An email has been sent to the user's email, click the link in that email. " ) ]
2021-08-05 03:26:14 +02:00
MustConfirmEmail ,
/// Must provide an SMS code
2021-08-10 00:44:42 +02:00
#[ error( " Awaiting finalization " ) ]
2021-08-05 03:26:14 +02:00
AwaitingFinalization ,
2021-08-10 00:44:42 +02:00
#[ error( " Authenticator is already present. " ) ]
2021-08-05 03:26:14 +02:00
AuthenticatorPresent ,
2021-08-10 00:44:42 +02:00
#[ error(transparent) ]
Unknown ( #[ from ] anyhow ::Error ) ,
2021-08-05 03:26:14 +02:00
}
2021-08-10 00:44:42 +02:00
#[ derive(Error, Debug) ]
2021-08-09 06:09:34 +02:00
pub enum FinalizeLinkError {
2021-08-10 00:44:42 +02:00
#[ error( " Provided SMS code was incorrect. " ) ]
2021-08-09 06:09:34 +02:00
BadSmsCode ,
/// Steam wants more 2fa codes to verify that we can generate valid codes. Call finalize again.
2021-08-10 00:44:42 +02:00
#[ error( " Steam wants more 2fa codes for verification. " ) ]
2021-08-09 06:09:34 +02:00
WantMore ,
2021-08-10 00:44:42 +02:00
#[ error( " Finalization was not successful. Status code {status:?} " ) ]
Failure { status : i32 } ,
#[ error(transparent) ]
Unknown ( #[ from ] anyhow ::Error ) ,
2021-08-05 03:26:14 +02:00
}