setup: add support for different link confirmation types, and verify that the authenticator was actually set up (#348)

fixes #345
This commit is contained in:
Carson McManus 2023-12-03 11:36:35 -05:00 committed by GitHub
parent 63805293ff
commit 166b7a908c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 97 additions and 15 deletions

View file

@ -196,14 +196,14 @@ impl AccountManager {
Ok(())
}
pub fn remove_account(&mut self, account_name: String) {
pub fn remove_account(&mut self, account_name: &String) {
let index = self
.manifest
.entries
.iter()
.position(|a| a.account_name == account_name)
.position(|a| &a.account_name == account_name)
.unwrap();
self.accounts.remove(&account_name);
self.accounts.remove(account_name);
self.manifest.entries.remove(index);
}

View file

@ -94,7 +94,7 @@ where
}
for account_name in successful {
manager.remove_account(account_name);
manager.remove_account(&account_name);
}
manager.save()?;

View file

@ -2,8 +2,11 @@ use log::*;
use phonenumber::PhoneNumber;
use secrecy::ExposeSecret;
use steamguard::{
accountlinker::AccountLinkSuccess, phonelinker::PhoneLinker, steamapi::PhoneClient,
token::Tokens, AccountLinkError, AccountLinker, FinalizeLinkError,
accountlinker::{AccountLinkConfirmType, AccountLinkSuccess},
phonelinker::PhoneLinker,
steamapi::PhoneClient,
token::Tokens,
AccountLinkError, AccountLinker, FinalizeLinkError,
};
use crate::{tui, AccountManager};
@ -48,6 +51,7 @@ where
break;
}
Err(AccountLinkError::MustProvidePhoneNumber) => {
// As of Dec 12, 2023, Steam no longer appears to require a phone number to add an authenticator. Keeping this code here just in case.
eprintln!("Looks like you don't have a phone number on this account.");
do_add_phone_number(transport.clone(), linker.tokens())?;
}
@ -66,6 +70,7 @@ where
}
let mut server_time = link.server_time();
let phone_number_hint = link.phone_number_hint().to_owned();
let confirm_type = link.confirm_type();
manager.add_account(link.into_account());
match manager.save() {
Ok(_) => {}
@ -88,15 +93,29 @@ where
tui::pause();
debug!("attempting link finalization");
println!(
"A code has been sent to your phone number ending in {}.",
phone_number_hint
);
print!("Enter SMS code: ");
let sms_code = tui::prompt();
let confirm_code = match confirm_type {
AccountLinkConfirmType::Email => {
eprintln!(
"A code has been sent to the email address associated with this account."
);
tui::prompt_non_empty("Enter email code: ")
}
AccountLinkConfirmType::SMS => {
eprintln!(
"A code has been sent to your phone number ending in {}.",
phone_number_hint
);
tui::prompt_non_empty("Enter SMS code: ")
}
AccountLinkConfirmType::Unknown(t) => {
error!("Unknown link confirm type: {}", t);
bail!("Unknown link confirm type: {}", t);
}
};
let mut tries = 0;
loop {
match linker.finalize(server_time, &mut account, sms_code.clone()) {
match linker.finalize(server_time, &mut account, confirm_code.clone()) {
Ok(_) => break,
Err(FinalizeLinkError::WantMore { server_time: s }) => {
server_time = s;
@ -116,6 +135,18 @@ where
let revocation_code = account.revocation_code.clone();
drop(account); // explicitly drop the lock so we don't hang on the mutex
info!("Verifying authenticator status...");
let status =
linker.query_status(&manager.get_account(&account_name).unwrap().lock().unwrap())?;
if status.state() == 0 {
debug!(
"authenticator state: {} -- did not actually finalize",
status.state()
);
manager.remove_account(&account_name);
bail!("Authenticator finalization was unsuccessful. You may have entered the wrong confirm code in the previous step. Try again.");
}
info!("Authenticator finalized.");
match manager.save() {
Ok(_) => {}

View file

@ -34,6 +34,16 @@ pub(crate) fn prompt() -> String {
line
}
pub(crate) fn prompt_non_empty(prompt_text: impl AsRef<str>) -> String {
loop {
eprint!("{}", prompt_text.as_ref());
let input = prompt();
if !input.is_empty() {
return input;
}
}
}
/// Prompt the user for a single character response. Useful for asking yes or no questions.
///
/// `chars` should be all lowercase characters, with at most 1 uppercase character. The uppercase character is the default answer if no answer is provided.

View file

@ -1,5 +1,6 @@
use crate::protobufs::service_twofactor::{
CTwoFactor_AddAuthenticator_Request, CTwoFactor_FinalizeAddAuthenticator_Request,
CTwoFactor_Status_Request, CTwoFactor_Status_Response,
};
use crate::steamapi::twofactor::TwoFactorClient;
use crate::token::TwoFactorSecret;
@ -85,6 +86,7 @@ where
account,
server_time: resp.server_time(),
phone_number_hint: resp.take_phone_number_hint(),
confirm_type: resp.confirm_type().into(),
};
Ok(success)
}
@ -94,7 +96,7 @@ where
&mut self,
time: u64,
account: &mut SteamGuardAccount,
sms_code: String,
confirm_code: String,
) -> anyhow::Result<(), FinalizeLinkError> {
let code = account.generate_code(time);
@ -105,7 +107,8 @@ where
req.set_steamid(steam_id);
req.set_authenticator_code(code);
req.set_authenticator_time(time);
req.set_activation_code(sms_code);
req.set_activation_code(confirm_code);
req.set_validate_sms_code(true);
let resp = self.client.finalize_authenticator(req, token)?;
@ -124,6 +127,21 @@ where
self.finalized = true;
Ok(())
}
pub fn query_status(
&self,
account: &SteamGuardAccount,
) -> anyhow::Result<CTwoFactor_Status_Response> {
let mut req = CTwoFactor_Status_Request::new();
req.set_steamid(account.steam_id);
let resp = self
.client
.query_status(req, self.tokens.access_token())
.unwrap();
Ok(resp.into_response_data())
}
}
#[derive(Debug)]
@ -131,6 +149,7 @@ pub struct AccountLinkSuccess {
account: SteamGuardAccount,
server_time: u64,
phone_number_hint: String,
confirm_type: AccountLinkConfirmType,
}
impl AccountLinkSuccess {
@ -149,6 +168,28 @@ impl AccountLinkSuccess {
pub fn phone_number_hint(&self) -> &str {
&self.phone_number_hint
}
pub fn confirm_type(&self) -> AccountLinkConfirmType {
self.confirm_type
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[repr(i32)]
pub enum AccountLinkConfirmType {
SMS = 1,
Email = 3,
Unknown(i32),
}
impl From<i32> for AccountLinkConfirmType {
fn from(i: i32) -> Self {
match i {
1 => AccountLinkConfirmType::SMS,
3 => AccountLinkConfirmType::Email,
_ => AccountLinkConfirmType::Unknown(i),
}
}
}
fn generate_device_id() -> String {