Skip to content

Commit d864e1d

Browse files
authored
feat(setup): option to add phone number if authenticator transfer fails (#436)
1 parent 6720fd3 commit d864e1d

File tree

2 files changed

+58
-5
lines changed

2 files changed

+58
-5
lines changed

src/commands/setup.rs

+49-5
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ where
5454
do_add_phone_number(transport.clone(), linker.tokens())?;
5555
}
5656
Err(AccountLinkError::MustConfirmEmail) => {
57-
println!("Check your email and click the link.");
57+
eprintln!("Check your email and click the link.");
5858
tui::pause();
5959
}
6060
Err(AccountLinkError::AuthenticatorPresent) => {
@@ -69,11 +69,55 @@ where
6969
eprintln!("[A] Abort setup");
7070
let answer = tui::prompt_char("What would you like to do?", "Tra");
7171
match answer {
72-
't' => return Self::transfer_new_account(linker, manager),
72+
't' => {
73+
let mut already_added_phone_number = false;
74+
loop {
75+
if let Err(err) = Self::transfer_new_account(&mut linker, manager) {
76+
if !already_added_phone_number {
77+
error!("Failed to transfer authenticator. {}", err);
78+
info!("There's nothing else to be done right now. Wait a few minutes and try again.");
79+
match tui::prompt_char("Would you like to try again?", "yN")
80+
{
81+
'y' => {
82+
continue;
83+
}
84+
_ => debug!("Declined, aborting."),
85+
}
86+
return Err(err);
87+
}
88+
info!("I can't check if you already have a phone number, but I can try to add one for you.");
89+
90+
match tui::prompt_char(
91+
"Would you like to add a phone number to this account?",
92+
"yN",
93+
) {
94+
'y' => {
95+
do_add_phone_number(
96+
transport.clone(),
97+
linker.tokens(),
98+
)?;
99+
info!("Lets try the transfer again. Pausing for 20 seconds to let Steam catch up...");
100+
already_added_phone_number = true;
101+
// I haven't actually rigorously tested how long it takes for Steam to propagate this change. This is a guess.
102+
// 3 seconds is definitely too short (tested).
103+
std::thread::sleep(std::time::Duration::from_secs(20));
104+
continue;
105+
}
106+
_ => debug!("Declined, aborting."),
107+
}
108+
109+
return Err(err);
110+
}
111+
112+
return Ok(());
113+
}
114+
}
73115
'r' => {
74116
loop {
117+
// TODO: keep track of codes already attempted and don't allow them to be used again to avoid consuming attempts.
75118
let revocation_code =
76119
tui::prompt_non_empty("Enter your revocation code (R#####): ");
120+
// TODO: revocation code must start with an R and be 5 digits. Warn if it doesn't, and allow the user to correct it before proceeding.
77121
match linker.remove_authenticator(Some(&revocation_code)) {
78122
Ok(_) => break,
79123
Err(RemoveAuthenticatorError::IncorrectRevocationCode {
@@ -218,7 +262,7 @@ impl SetupCommand {
218262

219263
/// Transfer an existing authenticator to steamguard-cli.
220264
fn transfer_new_account<T>(
221-
mut linker: AccountLinker<T>,
265+
linker: &mut AccountLinker<T>,
222266
manager: &mut AccountManager,
223267
) -> anyhow::Result<()>
224268
where
@@ -287,7 +331,7 @@ pub fn do_add_phone_number<T: Transport>(transport: T, tokens: &Tokens) -> anyho
287331
let resp = linker.set_account_phone_number(phone_number)?;
288332

289333
eprintln!(
290-
"Please click the link in the email sent to {}",
334+
"Please click the link in the email sent to {}. Once you've done that, you can continue.",
291335
resp.confirmation_email_address()
292336
);
293337
tui::pause();
@@ -307,7 +351,7 @@ pub fn do_add_phone_number<T: Transport>(transport: T, tokens: &Tokens) -> anyho
307351
}
308352
}
309353

310-
info!("Successfully added phone number to account");
354+
info!("Successfully added phone number to account.");
311355

312356
Ok(())
313357
}

steamguard/src/accountlinker.rs

+9
Original file line numberDiff line numberDiff line change
@@ -179,6 +179,8 @@ where
179179
/// Begin the process of "transfering" a mobile authenticator from a different device to this device.
180180
///
181181
/// "Transfering" does not actually literally transfer the secrets from one device to another. Instead, it generates a new set of secrets on this device, and invalidates the old secrets on the other device. Call [`Self::transfer_finish`] to complete the process.
182+
///
183+
/// As of 2025-02-07, transfering an authenticator requires a phone number to be present on the account. If there is no phone number on the account, this method will return an error.
182184
pub fn transfer_start(&mut self) -> Result<(), TransferError> {
183185
let req = CTwoFactor_RemoveAuthenticatorViaChallengeStart_Request::new();
184186
let resp = self
@@ -368,6 +370,12 @@ impl From<EResult> for RemoveAuthenticatorError {
368370

369371
#[derive(Error, Debug)]
370372
pub enum TransferError {
373+
/// A generic failure. Anything could have happened, but we don't necessarily know what it would be.
374+
///
375+
/// Observed conditions when this error has occurred:
376+
/// - This can occur upon the start of an authenticator transfer if there is no phone number on the account.
377+
#[error("Generic failure. Steam did not provide any additional information.")]
378+
GenericFailure,
371379
#[error("Provided SMS code was incorrect.")]
372380
BadSmsCode,
373381
#[error("Failed to send request to Steam: {0:?}")]
@@ -381,6 +389,7 @@ pub enum TransferError {
381389
impl From<EResult> for TransferError {
382390
fn from(result: EResult) -> Self {
383391
match result {
392+
EResult::Fail => TransferError::GenericFailure,
384393
EResult::SMSCodeFailed => TransferError::BadSmsCode,
385394
r => TransferError::UnknownEResult(r),
386395
}

0 commit comments

Comments
 (0)