remove: refactor to make it more reliable (#257)

fixes #256
This commit is contained in:
Carson McManus 2023-06-30 10:53:05 -04:00 committed by GitHub
parent 71d399f645
commit b3c6639146
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 87 additions and 37 deletions

View file

@ -1,6 +1,7 @@
use std::sync::{Arc, Mutex}; use std::sync::{Arc, Mutex};
use log::*; use log::*;
use steamguard::{transport::TransportError, RemoveAuthenticatorError};
use crate::{errors::UserError, tui, AccountManager}; use crate::{errors::UserError, tui, AccountManager};
@ -37,35 +38,51 @@ impl AccountCommand for RemoveCommand {
let mut successful = vec![]; let mut successful = vec![];
for a in accounts { for a in accounts {
let mut account = a.lock().unwrap(); let mut account = a.lock().unwrap();
if !account.is_logged_in() {
info!("Account does not have tokens, logging in");
crate::do_login(&mut account)?;
}
match account.remove_authenticator(None) { let mut revocation: Option<String> = None;
Ok(success) => { loop {
if success { match account.remove_authenticator(revocation.as_ref()) {
println!("Removed authenticator from {}", account.account_name); Ok(_) => {
info!("Removed authenticator from {}", account.account_name);
successful.push(account.account_name.clone()); successful.push(account.account_name.clone());
} else { break;
println!( }
"Failed to remove authenticator from {}", Err(RemoveAuthenticatorError::TransportError(TransportError::Unauthorized)) => {
error!("Account {} is not logged in", account.account_name);
crate::do_login(&mut account)?;
continue;
}
Err(RemoveAuthenticatorError::IncorrectRevocationCode {
attempts_remaining,
}) => {
error!(
"Revocation code was incorrect for {} ({} attempts remaining)",
account.account_name, attempts_remaining
);
if attempts_remaining == 0 {
error!("No attempts remaining, aborting!");
break;
}
eprint!("Enter the revocation code for {}: ", account.account_name);
let code = tui::prompt();
revocation = Some(code);
}
Err(RemoveAuthenticatorError::MissingRevocationCode) => {
error!(
"Account {} does not have a revocation code",
account.account_name account.account_name
); );
if tui::prompt_char( eprint!("Enter the revocation code for {}: ", account.account_name);
"Would you like to remove it from the manifest anyway?", let code = tui::prompt();
"yN", revocation = Some(code);
) == 'y'
{
successful.push(account.account_name.clone());
}
}
} }
Err(err) => { Err(err) => {
error!( error!(
"Unexpected error when removing authenticator from {}: {}", "Unexpected error when removing authenticator from {}: {}",
account.account_name, err account.account_name, err
); );
break;
}
} }
} }
} }

View file

@ -10,6 +10,7 @@ pub use secrecy::{ExposeSecret, SecretString};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::io::Read; use std::io::Read;
use token::Tokens; use token::Tokens;
use transport::TransportError;
pub use userlogin::{DeviceDetails, LoginError, UserLogin}; pub use userlogin::{DeviceDetails, LoginError, UserLogin};
#[macro_use] #[macro_use]
@ -98,24 +99,56 @@ impl SteamGuardAccount {
/// Removes the mobile authenticator from the steam account. If this operation succeeds, this object can no longer be considered valid. /// Removes the mobile authenticator from the steam account. If this operation succeeds, this object can no longer be considered valid.
/// Returns whether or not the operation was successful. /// Returns whether or not the operation was successful.
pub fn remove_authenticator(&self, revocation_code: Option<String>) -> anyhow::Result<bool> { pub fn remove_authenticator(
ensure!( &self,
matches!(revocation_code, Some(_)) || !self.revocation_code.expose_secret().is_empty(), revocation_code: Option<&String>,
"Revocation code not provided." ) -> Result<(), RemoveAuthenticatorError> {
); if !matches!(revocation_code, Some(_)) && self.revocation_code.expose_secret().is_empty() {
return Err(RemoveAuthenticatorError::MissingRevocationCode);
}
let Some(tokens) = &self.tokens else { let Some(tokens) = &self.tokens else {
return Err(anyhow!("Tokens not set, login required")); return Err(RemoveAuthenticatorError::TransportError(TransportError::Unauthorized));
}; };
let mut client = TwoFactorClient::new(WebApiTransport::new()); let mut client = TwoFactorClient::new(WebApiTransport::new());
let mut req = CTwoFactor_RemoveAuthenticator_Request::new(); let mut req = CTwoFactor_RemoveAuthenticator_Request::new();
req.set_revocation_code( req.set_revocation_code(
revocation_code.unwrap_or(self.revocation_code.expose_secret().to_owned()), revocation_code
.unwrap_or(self.revocation_code.expose_secret())
.to_owned(),
); );
let resp = client.remove_authenticator(req, tokens.access_token())?; let resp = client.remove_authenticator(req, tokens.access_token())?;
if resp.result != EResult::OK {
Err(anyhow!("Failed to remove authenticator: {:?}", resp.result)) // returns EResult::TwoFactorCodeMismatch if the revocation code is incorrect
} else { if resp.result != EResult::OK && resp.result != EResult::TwoFactorCodeMismatch {
Ok(true) return Err(resp.result.into());
}
let resp = resp.into_response_data();
if !resp.success() {
return Err(RemoveAuthenticatorError::IncorrectRevocationCode {
attempts_remaining: resp.revocation_attempts_remaining(),
});
}
Ok(())
} }
} }
#[derive(Debug, thiserror::Error)]
pub enum RemoveAuthenticatorError {
#[error("Missing revocation code")]
MissingRevocationCode,
#[error("Incorrect revocation code, {attempts_remaining} attempts remaining")]
IncorrectRevocationCode { attempts_remaining: u32 },
#[error("Transport error: {0}")]
TransportError(#[from] TransportError),
#[error("Steam returned an enexpected result: {0:?}")]
UnknownEResult(EResult),
#[error("Unexpected error: {0}")]
Unknown(#[from] anyhow::Error),
}
impl From<EResult> for RemoveAuthenticatorError {
fn from(e: EResult) -> Self {
Self::UnknownEResult(e)
}
} }

View file

@ -1,5 +1,5 @@
use crate::token::Jwt; use crate::token::Jwt;
use crate::transport::Transport; use crate::transport::{Transport, TransportError};
use super::{ApiRequest, ApiResponse, BuildableRequest}; use super::{ApiRequest, ApiResponse, BuildableRequest};
@ -59,7 +59,7 @@ where
&mut self, &mut self,
req: CTwoFactor_RemoveAuthenticator_Request, req: CTwoFactor_RemoveAuthenticator_Request,
access_token: &Jwt, access_token: &Jwt,
) -> anyhow::Result<ApiResponse<CTwoFactor_RemoveAuthenticator_Response>> { ) -> Result<ApiResponse<CTwoFactor_RemoveAuthenticator_Response>, TransportError> {
let req = ApiRequest::new(SERVICE_NAME, "RemoveAuthenticator", 1, req) let req = ApiRequest::new(SERVICE_NAME, "RemoveAuthenticator", 1, req)
.with_access_token(access_token); .with_access_token(access_token);
let resp = self let resp = self