Skip to main content

steamkit/steam/authentication/
user_console_authenticator.rs

1//! An [`IAuthenticator`] that asks for the codes on the console.
2//!
3//! Input is read with `tokio::io::stdin`, so a worker thread isn't blocked
4//! while the user types.
5
6use async_trait::async_trait;
7use tokio::io::{AsyncBufReadExt, AsyncWriteExt, BufReader, Stdin};
8use tokio::sync::Mutex;
9
10use crate::error::SteamError;
11
12use super::i_authenticator::IAuthenticator;
13
14/// An [`IAuthenticator`] that asks for the 2-factor codes on the console.
15#[derive(Debug)]
16pub struct UserConsoleAuthenticator {
17    reader: Mutex<BufReader<Stdin>>,
18}
19
20impl Default for UserConsoleAuthenticator {
21    fn default() -> Self {
22        Self::new()
23    }
24}
25
26impl UserConsoleAuthenticator {
27    /// Creates a new authenticator.
28    pub fn new() -> Self {
29        UserConsoleAuthenticator {
30            reader: Mutex::new(BufReader::new(tokio::io::stdin())),
31        }
32    }
33
34    /// Reads a line from the console. Returns `None` at the end of input.
35    async fn read_line(&self) -> Result<Option<String>, SteamError> {
36        let mut line = String::new();
37
38        let read = {
39            let mut reader = self.reader.lock().await;
40            reader.read_line(&mut line).await?
41        };
42
43        if read == 0 {
44            return Ok(None);
45        }
46
47        Ok(Some(line.trim().to_string()))
48    }
49
50    /// Writes a prompt to stderr.
51    async fn write_prompt(prompt: &str) -> Result<(), SteamError> {
52        let mut stderr = tokio::io::stderr();
53        stderr.write_all(prompt.as_bytes()).await?;
54        stderr.flush().await?;
55
56        Ok(())
57    }
58}
59
60#[async_trait]
61impl IAuthenticator for UserConsoleAuthenticator {
62    /// Prompts the user for the 2-factor code of their authenticator app.
63    async fn get_device_code(
64        &self,
65        previous_code_was_incorrect: bool,
66    ) -> Result<String, SteamError> {
67        if previous_code_was_incorrect {
68            Self::write_prompt("The previous 2-factor auth code you have provided is incorrect.\n")
69                .await?;
70        }
71
72        loop {
73            Self::write_prompt(
74                "STEAM GUARD! Please enter your 2-factor auth code from your authenticator app: ",
75            )
76            .await?;
77
78            let Some(code) = self.read_line().await? else {
79                return Ok(String::new());
80            };
81
82            if !code.is_empty() {
83                return Ok(code);
84            }
85        }
86    }
87
88    /// Prompts the user for the Steam Guard code that was sent to their email.
89    async fn get_email_code(
90        &self,
91        email: &str,
92        previous_code_was_incorrect: bool,
93    ) -> Result<String, SteamError> {
94        if previous_code_was_incorrect {
95            Self::write_prompt("The previous 2-factor auth code you have provided is incorrect.\n")
96                .await?;
97        }
98
99        loop {
100            Self::write_prompt(&format!(
101                "STEAM GUARD! Please enter the auth code sent to the email at {email}: "
102            ))
103            .await?;
104
105            let Some(code) = self.read_line().await? else {
106                return Ok(String::new());
107            };
108
109            if !code.is_empty() {
110                return Ok(code);
111            }
112        }
113    }
114
115    /// Tells the user to confirm the sign-in in the Steam mobile app.
116    async fn accept_device_confirmation(&self) -> Result<bool, SteamError> {
117        Self::write_prompt("STEAM GUARD! Use the Steam Mobile App to confirm your sign in...\n")
118            .await?;
119
120        Ok(true)
121    }
122}