remove: move main remove_authenticator request logic into AccountLinker (#353)
This commit is contained in:
parent
9358fb60c9
commit
68dcedaeb7
4 changed files with 73 additions and 64 deletions
|
@ -1,7 +1,7 @@
|
|||
use std::sync::{Arc, Mutex};
|
||||
|
||||
use log::*;
|
||||
use steamguard::{steamapi::TwoFactorClient, transport::TransportError, RemoveAuthenticatorError};
|
||||
use steamguard::{accountlinker::RemoveAuthenticatorError, transport::TransportError};
|
||||
|
||||
use crate::{errors::UserError, tui, AccountManager};
|
||||
|
||||
|
@ -43,11 +43,10 @@ where
|
|||
let mut successful = vec![];
|
||||
for a in accounts {
|
||||
let mut account = a.lock().unwrap();
|
||||
let client = TwoFactorClient::new(transport.clone());
|
||||
|
||||
let mut revocation: Option<String> = None;
|
||||
loop {
|
||||
match account.remove_authenticator(&client, revocation.as_ref()) {
|
||||
match account.remove_authenticator(transport.clone(), revocation.as_ref()) {
|
||||
Ok(_) => {
|
||||
info!("Removed authenticator from {}", account.account_name);
|
||||
successful.push(account.account_name.clone());
|
||||
|
@ -69,17 +68,17 @@ where
|
|||
error!("No attempts remaining, aborting!");
|
||||
break;
|
||||
}
|
||||
eprint!("Enter the revocation code for {}: ", account.account_name);
|
||||
let code = tui::prompt();
|
||||
let code = tui::prompt_non_empty(format!(
|
||||
"Enter the revocation code for {}: ",
|
||||
account.account_name
|
||||
));
|
||||
revocation = Some(code);
|
||||
}
|
||||
Err(RemoveAuthenticatorError::MissingRevocationCode) => {
|
||||
error!(
|
||||
"Account {} does not have a revocation code",
|
||||
let code = tui::prompt_non_empty(format!(
|
||||
"Enter the revocation code for {}: ",
|
||||
account.account_name
|
||||
);
|
||||
eprint!("Enter the revocation code for {}: ", account.account_name);
|
||||
let code = tui::prompt();
|
||||
));
|
||||
revocation = Some(code);
|
||||
}
|
||||
Err(err) => {
|
||||
|
|
|
@ -47,6 +47,7 @@ pub(crate) fn prompt_non_empty(prompt_text: impl AsRef<str>) -> String {
|
|||
/// 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.
|
||||
/// The selected character returned will always be lowercase.
|
||||
pub(crate) fn prompt_char(text: &str, chars: &str) -> char {
|
||||
loop {
|
||||
let _ = stderr().queue(Print(format!("{} [{}] ", text, chars)));
|
||||
|
@ -58,10 +59,7 @@ pub(crate) fn prompt_char(text: &str, chars: &str) -> char {
|
|||
}
|
||||
}
|
||||
|
||||
fn prompt_char_impl<T>(input: T, chars: &str) -> anyhow::Result<char>
|
||||
where
|
||||
T: Into<String>,
|
||||
{
|
||||
fn prompt_char_impl(input: impl Into<String>, chars: &str) -> anyhow::Result<char> {
|
||||
let uppers = chars.replace(char::is_lowercase, "");
|
||||
if uppers.len() > 1 {
|
||||
panic!("Invalid chars for prompt_char. Maximum 1 uppercase letter is allowed.");
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
use crate::protobufs::service_twofactor::{
|
||||
CTwoFactor_AddAuthenticator_Request, CTwoFactor_FinalizeAddAuthenticator_Request,
|
||||
CTwoFactor_Status_Request, CTwoFactor_Status_Response,
|
||||
CTwoFactor_RemoveAuthenticator_Request, CTwoFactor_Status_Request, CTwoFactor_Status_Response,
|
||||
};
|
||||
use crate::steamapi::twofactor::TwoFactorClient;
|
||||
use crate::token::TwoFactorSecret;
|
||||
use crate::transport::Transport;
|
||||
use crate::transport::{Transport, TransportError};
|
||||
use crate::{steamapi::EResult, token::Tokens, SteamGuardAccount};
|
||||
use anyhow::Context;
|
||||
use base64::Engine;
|
||||
|
@ -142,6 +142,36 @@ where
|
|||
|
||||
Ok(resp.into_response_data())
|
||||
}
|
||||
|
||||
pub fn remove_authenticator(
|
||||
&self,
|
||||
revocation_code: Option<&String>,
|
||||
) -> Result<(), RemoveAuthenticatorError> {
|
||||
let Some(revocation_code) = revocation_code else {
|
||||
return Err(RemoveAuthenticatorError::MissingRevocationCode);
|
||||
};
|
||||
if revocation_code.is_empty() {
|
||||
return Err(RemoveAuthenticatorError::MissingRevocationCode);
|
||||
}
|
||||
let mut req = CTwoFactor_RemoveAuthenticator_Request::new();
|
||||
req.set_revocation_code(revocation_code.clone());
|
||||
let resp = self
|
||||
.client
|
||||
.remove_authenticator(req, self.tokens.access_token())?;
|
||||
|
||||
// returns EResult::TwoFactorCodeMismatch if the revocation code is incorrect
|
||||
if resp.result != EResult::OK && resp.result != EResult::TwoFactorCodeMismatch {
|
||||
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)]
|
||||
|
@ -253,3 +283,23 @@ impl From<EResult> for FinalizeLinkError {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
use crate::protobufs::service_twofactor::CTwoFactor_RemoveAuthenticator_Request;
|
||||
use crate::steamapi::EResult;
|
||||
use crate::{steamapi::twofactor::TwoFactorClient, token::TwoFactorSecret};
|
||||
use crate::token::TwoFactorSecret;
|
||||
use accountlinker::RemoveAuthenticatorError;
|
||||
pub use accountlinker::{AccountLinkError, AccountLinker, FinalizeLinkError};
|
||||
pub use confirmation::*;
|
||||
pub use qrapprover::{QrApprover, QrApproverError};
|
||||
|
@ -96,58 +95,21 @@ impl SteamGuardAccount {
|
|||
|
||||
/// 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.
|
||||
pub fn remove_authenticator<T: Transport>(
|
||||
///
|
||||
/// A convenience method for [`AccountLinker::remove_authenticator`].
|
||||
pub fn remove_authenticator(
|
||||
&self,
|
||||
client: &TwoFactorClient<T>,
|
||||
transport: impl Transport,
|
||||
revocation_code: Option<&String>,
|
||||
) -> Result<(), RemoveAuthenticatorError> {
|
||||
if revocation_code.is_none() && self.revocation_code.expose_secret().is_empty() {
|
||||
return Err(RemoveAuthenticatorError::MissingRevocationCode);
|
||||
}
|
||||
let Some(tokens) = &self.tokens else {
|
||||
return Err(RemoveAuthenticatorError::TransportError(
|
||||
TransportError::Unauthorized,
|
||||
));
|
||||
};
|
||||
let mut req = CTwoFactor_RemoveAuthenticator_Request::new();
|
||||
req.set_revocation_code(
|
||||
revocation_code
|
||||
.unwrap_or(self.revocation_code.expose_secret())
|
||||
.to_owned(),
|
||||
);
|
||||
let resp = client.remove_authenticator(req, tokens.access_token())?;
|
||||
|
||||
// returns EResult::TwoFactorCodeMismatch if the revocation code is incorrect
|
||||
if resp.result != EResult::OK && resp.result != EResult::TwoFactorCodeMismatch {
|
||||
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)
|
||||
let revocation_code =
|
||||
Some(revocation_code.unwrap_or_else(|| self.revocation_code.expose_secret()));
|
||||
let linker = AccountLinker::new(transport, tokens.clone());
|
||||
linker.remove_authenticator(revocation_code)
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue