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 std::sync::{Arc, Mutex};
|
||||||
|
|
||||||
use log::*;
|
use log::*;
|
||||||
use steamguard::{steamapi::TwoFactorClient, transport::TransportError, RemoveAuthenticatorError};
|
use steamguard::{accountlinker::RemoveAuthenticatorError, transport::TransportError};
|
||||||
|
|
||||||
use crate::{errors::UserError, tui, AccountManager};
|
use crate::{errors::UserError, tui, AccountManager};
|
||||||
|
|
||||||
|
@ -43,11 +43,10 @@ where
|
||||||
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();
|
||||||
let client = TwoFactorClient::new(transport.clone());
|
|
||||||
|
|
||||||
let mut revocation: Option<String> = None;
|
let mut revocation: Option<String> = None;
|
||||||
loop {
|
loop {
|
||||||
match account.remove_authenticator(&client, revocation.as_ref()) {
|
match account.remove_authenticator(transport.clone(), revocation.as_ref()) {
|
||||||
Ok(_) => {
|
Ok(_) => {
|
||||||
info!("Removed authenticator from {}", account.account_name);
|
info!("Removed authenticator from {}", account.account_name);
|
||||||
successful.push(account.account_name.clone());
|
successful.push(account.account_name.clone());
|
||||||
|
@ -69,17 +68,17 @@ where
|
||||||
error!("No attempts remaining, aborting!");
|
error!("No attempts remaining, aborting!");
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
eprint!("Enter the revocation code for {}: ", account.account_name);
|
let code = tui::prompt_non_empty(format!(
|
||||||
let code = tui::prompt();
|
"Enter the revocation code for {}: ",
|
||||||
|
account.account_name
|
||||||
|
));
|
||||||
revocation = Some(code);
|
revocation = Some(code);
|
||||||
}
|
}
|
||||||
Err(RemoveAuthenticatorError::MissingRevocationCode) => {
|
Err(RemoveAuthenticatorError::MissingRevocationCode) => {
|
||||||
error!(
|
let code = tui::prompt_non_empty(format!(
|
||||||
"Account {} does not have a revocation code",
|
"Enter the revocation code for {}: ",
|
||||||
account.account_name
|
account.account_name
|
||||||
);
|
));
|
||||||
eprint!("Enter the revocation code for {}: ", account.account_name);
|
|
||||||
let code = tui::prompt();
|
|
||||||
revocation = Some(code);
|
revocation = Some(code);
|
||||||
}
|
}
|
||||||
Err(err) => {
|
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.
|
/// 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.
|
/// `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 {
|
pub(crate) fn prompt_char(text: &str, chars: &str) -> char {
|
||||||
loop {
|
loop {
|
||||||
let _ = stderr().queue(Print(format!("{} [{}] ", text, chars)));
|
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>
|
fn prompt_char_impl(input: impl Into<String>, chars: &str) -> anyhow::Result<char> {
|
||||||
where
|
|
||||||
T: Into<String>,
|
|
||||||
{
|
|
||||||
let uppers = chars.replace(char::is_lowercase, "");
|
let uppers = chars.replace(char::is_lowercase, "");
|
||||||
if uppers.len() > 1 {
|
if uppers.len() > 1 {
|
||||||
panic!("Invalid chars for prompt_char. Maximum 1 uppercase letter is allowed.");
|
panic!("Invalid chars for prompt_char. Maximum 1 uppercase letter is allowed.");
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
use crate::protobufs::service_twofactor::{
|
use crate::protobufs::service_twofactor::{
|
||||||
CTwoFactor_AddAuthenticator_Request, CTwoFactor_FinalizeAddAuthenticator_Request,
|
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::steamapi::twofactor::TwoFactorClient;
|
||||||
use crate::token::TwoFactorSecret;
|
use crate::token::TwoFactorSecret;
|
||||||
use crate::transport::Transport;
|
use crate::transport::{Transport, TransportError};
|
||||||
use crate::{steamapi::EResult, token::Tokens, SteamGuardAccount};
|
use crate::{steamapi::EResult, token::Tokens, SteamGuardAccount};
|
||||||
use anyhow::Context;
|
use anyhow::Context;
|
||||||
use base64::Engine;
|
use base64::Engine;
|
||||||
|
@ -142,6 +142,36 @@ where
|
||||||
|
|
||||||
Ok(resp.into_response_data())
|
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)]
|
#[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::token::TwoFactorSecret;
|
||||||
use crate::steamapi::EResult;
|
use accountlinker::RemoveAuthenticatorError;
|
||||||
use crate::{steamapi::twofactor::TwoFactorClient, token::TwoFactorSecret};
|
|
||||||
pub use accountlinker::{AccountLinkError, AccountLinker, FinalizeLinkError};
|
pub use accountlinker::{AccountLinkError, AccountLinker, FinalizeLinkError};
|
||||||
pub use confirmation::*;
|
pub use confirmation::*;
|
||||||
pub use qrapprover::{QrApprover, QrApproverError};
|
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.
|
/// 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<T: Transport>(
|
///
|
||||||
|
/// A convenience method for [`AccountLinker::remove_authenticator`].
|
||||||
|
pub fn remove_authenticator(
|
||||||
&self,
|
&self,
|
||||||
client: &TwoFactorClient<T>,
|
transport: impl Transport,
|
||||||
revocation_code: Option<&String>,
|
revocation_code: Option<&String>,
|
||||||
) -> Result<(), RemoveAuthenticatorError> {
|
) -> Result<(), RemoveAuthenticatorError> {
|
||||||
if revocation_code.is_none() && 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(RemoveAuthenticatorError::TransportError(
|
return Err(RemoveAuthenticatorError::TransportError(
|
||||||
TransportError::Unauthorized,
|
TransportError::Unauthorized,
|
||||||
));
|
));
|
||||||
};
|
};
|
||||||
let mut req = CTwoFactor_RemoveAuthenticator_Request::new();
|
let revocation_code =
|
||||||
req.set_revocation_code(
|
Some(revocation_code.unwrap_or_else(|| self.revocation_code.expose_secret()));
|
||||||
revocation_code
|
let linker = AccountLinker::new(transport, tokens.clone());
|
||||||
.unwrap_or(self.revocation_code.expose_secret())
|
linker.remove_authenticator(revocation_code)
|
||||||
.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)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue