2021-08-08 21:25:27 +02:00
use crate ::{
2022-12-06 16:02:07 +01:00
api_responses ::{ AddAuthenticatorResponse , FinalizeAddAuthenticatorResponse } ,
steamapi ::{ Session , SteamApiClient } ,
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-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 ,
2021-08-10 01:09:48 +02:00
pub 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 ,
2021-08-10 02:05:23 +02:00
session : session . clone ( ) ,
2022-06-19 20:42:07 +02:00
client : SteamApiClient ::new ( Some ( secrecy ::Secret ::new ( session ) ) ) ,
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-10 03:41:20 +02:00
// let has_phone = self.client.has_phone()?;
2021-08-09 00:32:50 +02:00
2021-08-10 03:41:20 +02:00
// if has_phone && !self.phone_number.is_empty() {
// return Err(AccountLinkError::MustRemovePhoneNumber);
// }
// if !has_phone && self.phone_number.is_empty() {
// return Err(AccountLinkError::MustProvidePhoneNumber);
// }
2021-08-09 00:32:50 +02:00
2021-08-10 03:41:20 +02:00
// if !has_phone {
// if self.sent_confirmation_email {
// if !self.client.check_email_confirmation()? {
// return Err(anyhow!("Failed email confirmation check"))?;
// }
// } else if !self.client.add_phone_number(self.phone_number.clone())? {
// return Err(anyhow!("Failed to add phone number"))?;
// } else {
// self.sent_confirmation_email = true;
// return Err(AccountLinkError::MustConfirmEmail);
// }
// }
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
}
2022-01-02 15:46:01 +01:00
2 = > {
2022-12-05 16:36:38 +01:00
// If the user has no phone number on their account, it will always return this status code.
// However, this does not mean that this status just means "no phone number". It can also
// be literally anything else, so that's why we return GenericFailure here.
2022-01-02 15:46:01 +01:00
return Err ( AccountLinkError ::GenericFailure ) ;
}
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 > {
2022-06-21 02:05:00 +02:00
let time = crate ::steamapi ::get_server_time ( ) ? . server_time ;
2021-08-09 06:09:34 +02:00
let code = account . generate_code ( time ) ;
2021-08-10 01:09:48 +02:00
let resp : FinalizeAddAuthenticatorResponse =
self . client
. finalize_authenticator ( sms_code . clone ( ) , code , time ) ? ;
2021-08-09 06:09:34 +02:00
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 01:09:48 +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 ,
2021-08-10 00:44:42 +02:00
#[ error( " Authenticator is already present. " ) ]
2021-08-05 03:26:14 +02:00
AuthenticatorPresent ,
2022-12-05 16:36:38 +01:00
#[ error( " Steam was unable to link the authenticator to the account. No additional information about this error is available. This is a Steam error, not a steamguard-cli error. Try adding a phone number to your Steam account (which you can do here: https://store.steampowered.com/phone/add), or try again later. " ) ]
2022-01-02 15:46:01 +01:00
GenericFailure ,
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:?} " ) ]
2021-08-10 01:09:48 +02:00
Failure { status : i32 } ,
2021-08-10 00:44:42 +02:00
#[ error(transparent) ]
Unknown ( #[ from ] anyhow ::Error ) ,
2021-08-05 03:26:14 +02:00
}