1use crate::auth::SteamGuardToken;
2use another_steam_totp::generate_auth_code;
3use futures_util::future::{select, Either};
4use std::pin::pin;
5use steam_vent_proto::steammessages_auth_steamclient::{
6 CAuthentication_AllowedConfirmation, EAuthSessionGuardType,
7};
8use tokio::io::AsyncBufReadExt;
9use tokio::io::{stdin, stdout, AsyncRead, AsyncWrite, AsyncWriteExt, BufReader, Stdin, Stdout};
10
11#[derive(Debug, Clone)]
13pub struct ConfirmationMethod(CAuthentication_AllowedConfirmation);
14
15impl From<CAuthentication_AllowedConfirmation> for ConfirmationMethod {
16 fn from(value: CAuthentication_AllowedConfirmation) -> Self {
17 Self(value)
18 }
19}
20
21impl ConfirmationMethod {
22 pub fn confirmation_type(&self) -> &'static str {
24 match self.0.confirmation_type() {
25 EAuthSessionGuardType::k_EAuthSessionGuardType_Unknown => "unknown",
26 EAuthSessionGuardType::k_EAuthSessionGuardType_None => "none",
27 EAuthSessionGuardType::k_EAuthSessionGuardType_EmailCode => "email",
28 EAuthSessionGuardType::k_EAuthSessionGuardType_DeviceCode => "device code",
29 EAuthSessionGuardType::k_EAuthSessionGuardType_DeviceConfirmation => {
30 "device confirmation"
31 }
32 EAuthSessionGuardType::k_EAuthSessionGuardType_EmailConfirmation => {
33 "email confirmation"
34 }
35 EAuthSessionGuardType::k_EAuthSessionGuardType_MachineToken => "machine token",
36 EAuthSessionGuardType::k_EAuthSessionGuardType_LegacyMachineAuth => "machine auth",
37 }
38 }
39
40 pub fn confirmation_details(&self) -> &str {
42 self.0.associated_message()
43 }
44
45 pub fn action_required(&self) -> bool {
47 self.0.confirmation_type() != EAuthSessionGuardType::k_EAuthSessionGuardType_None
48 }
49
50 pub fn class(&self) -> ConfirmationMethodClass {
52 match self.0.confirmation_type() {
53 EAuthSessionGuardType::k_EAuthSessionGuardType_Unknown => ConfirmationMethodClass::None,
54 EAuthSessionGuardType::k_EAuthSessionGuardType_None => ConfirmationMethodClass::None,
55 EAuthSessionGuardType::k_EAuthSessionGuardType_EmailCode => {
56 ConfirmationMethodClass::Code
57 }
58 EAuthSessionGuardType::k_EAuthSessionGuardType_DeviceCode => {
59 ConfirmationMethodClass::Code
60 }
61 EAuthSessionGuardType::k_EAuthSessionGuardType_DeviceConfirmation => {
62 ConfirmationMethodClass::Confirmation
63 }
64 EAuthSessionGuardType::k_EAuthSessionGuardType_EmailConfirmation => {
65 ConfirmationMethodClass::Confirmation
66 }
67 EAuthSessionGuardType::k_EAuthSessionGuardType_MachineToken => {
68 ConfirmationMethodClass::Stored
69 }
70 EAuthSessionGuardType::k_EAuthSessionGuardType_LegacyMachineAuth => {
71 ConfirmationMethodClass::Stored
72 }
73 }
74 }
75
76 pub fn token_type(&self) -> Option<GuardTokenType> {
78 match self.0.confirmation_type() {
79 EAuthSessionGuardType::k_EAuthSessionGuardType_Unknown => None,
80 EAuthSessionGuardType::k_EAuthSessionGuardType_None => None,
81 EAuthSessionGuardType::k_EAuthSessionGuardType_EmailCode => Some(GuardTokenType::Email),
82 EAuthSessionGuardType::k_EAuthSessionGuardType_DeviceCode => {
83 Some(GuardTokenType::Device)
84 }
85 EAuthSessionGuardType::k_EAuthSessionGuardType_DeviceConfirmation => None,
86 EAuthSessionGuardType::k_EAuthSessionGuardType_EmailConfirmation => None,
87 EAuthSessionGuardType::k_EAuthSessionGuardType_MachineToken => None,
88 EAuthSessionGuardType::k_EAuthSessionGuardType_LegacyMachineAuth => None,
89 }
90 }
91}
92
93#[derive(Eq, PartialEq, Debug, Clone)]
95pub enum ConfirmationMethodClass {
96 Code,
98 Confirmation,
100 Stored,
102 None,
104}
105
106#[non_exhaustive]
108#[derive(Debug)]
109pub enum ConfirmationAction {
110 GuardToken(SteamGuardToken, GuardTokenType),
112 None,
114 Abort,
116}
117
118#[derive(Debug)]
120pub enum GuardTokenType {
121 Email,
122 Device,
123}
124
125impl From<GuardTokenType> for EAuthSessionGuardType {
126 fn from(value: GuardTokenType) -> Self {
127 match value {
128 GuardTokenType::Device => EAuthSessionGuardType::k_EAuthSessionGuardType_DeviceCode,
129 GuardTokenType::Email => EAuthSessionGuardType::k_EAuthSessionGuardType_EmailCode,
130 }
131 }
132}
133
134pub trait AuthConfirmationHandler: Sized {
144 fn handle_confirmation(
152 self,
153 allowed_confirmations: &[ConfirmationMethod],
154 ) -> impl std::future::Future<Output = Option<ConfirmationAction>> + Send;
155
156 fn or<Right: AuthConfirmationHandler>(
160 self,
161 other: Right,
162 ) -> EitherConfirmationHandler<Self, Right> {
163 EitherConfirmationHandler::new(self, other)
164 }
165}
166
167pub type ConsoleAuthConfirmationHandler = UserProvidedAuthConfirmationHandler<Stdin, Stdout>;
169
170pub struct UserProvidedAuthConfirmationHandler<Read, Write> {
172 input: BufReader<Read>,
173 output: Write,
174}
175
176impl Default for ConsoleAuthConfirmationHandler {
177 fn default() -> Self {
178 ConsoleAuthConfirmationHandler {
179 input: BufReader::new(stdin()),
180 output: stdout(),
181 }
182 }
183}
184
185impl<Read, Write> UserProvidedAuthConfirmationHandler<Read, Write>
186where
187 Read: AsyncRead + Unpin + Send + Sync,
188 Write: AsyncWrite + Unpin + Send + Sync,
189{
190 pub fn new(input: Read, output: Write) -> Self {
195 UserProvidedAuthConfirmationHandler {
196 input: BufReader::new(input),
197 output,
198 }
199 }
200}
201
202impl<Read, Write> AuthConfirmationHandler for UserProvidedAuthConfirmationHandler<Read, Write>
203where
204 Read: AsyncRead + Unpin + Send + Sync,
205 Write: AsyncWrite + Unpin + Send + Sync,
206{
207 async fn handle_confirmation(
208 mut self,
209 allowed_confirmations: &[ConfirmationMethod],
210 ) -> Option<ConfirmationAction> {
211 for method in allowed_confirmations {
212 if let Some(token_type) = method.token_type() {
213 let msg = format!(
214 "{}: {}",
215 method.confirmation_type(),
216 method.confirmation_details()
217 );
218 self.output.write_all(msg.as_bytes()).await.ok();
219 self.output.flush().await.ok();
220 let mut buff = String::with_capacity(16);
221 self.input.read_line(&mut buff).await.ok();
222 buff.truncate(buff.trim().len());
223 return if buff.is_empty() {
224 Some(ConfirmationAction::Abort)
225 } else {
226 let token = SteamGuardToken(buff);
227 Some(ConfirmationAction::GuardToken(token, token_type))
228 };
229 }
230 }
231 None
232 }
233}
234
235pub struct SharedSecretAuthConfirmationHandler {
239 shared_secret: String,
240}
241
242impl SharedSecretAuthConfirmationHandler {
243 pub fn new(shared_secret: &str) -> Self {
247 SharedSecretAuthConfirmationHandler {
248 shared_secret: shared_secret.into(),
249 }
250 }
251}
252
253impl AuthConfirmationHandler for SharedSecretAuthConfirmationHandler {
254 async fn handle_confirmation(
255 self,
256 allowed_confirmations: &[ConfirmationMethod],
257 ) -> Option<ConfirmationAction> {
258 for method in allowed_confirmations {
259 if let Some(token_type) = method.token_type() {
260 let auth_code = generate_auth_code(self.shared_secret, None)
261 .expect("Could not generate auth code given shared secret.");
262 let token = SteamGuardToken(auth_code);
263 return Some(ConfirmationAction::GuardToken(token, token_type));
264 }
265 }
266 None
267 }
268}
269
270#[derive(Default)]
272pub struct DeviceConfirmationHandler;
273
274impl AuthConfirmationHandler for DeviceConfirmationHandler {
275 async fn handle_confirmation(
276 self,
277 allowed_confirmations: &[ConfirmationMethod],
278 ) -> Option<ConfirmationAction> {
279 for method in allowed_confirmations {
280 if method.class() == ConfirmationMethodClass::Confirmation {
281 return Some(ConfirmationAction::None);
282 }
283 }
284 None
285 }
286}
287
288pub struct EitherConfirmationHandler<Left, Right> {
293 left: Left,
294 right: Right,
295}
296
297impl<Left, Right> EitherConfirmationHandler<Left, Right> {
298 pub fn new(left: Left, right: Right) -> Self {
299 Self { left, right }
300 }
301}
302
303impl<Left, Right> AuthConfirmationHandler for EitherConfirmationHandler<Left, Right>
304where
305 Left: AuthConfirmationHandler + Send + Sync,
306 Right: AuthConfirmationHandler + Send + Sync,
307{
308 async fn handle_confirmation(
309 self,
310 allowed_confirmations: &[ConfirmationMethod],
311 ) -> Option<ConfirmationAction> {
312 match select(
313 pin!(self.left.handle_confirmation(allowed_confirmations)),
314 pin!(self.right.handle_confirmation(allowed_confirmations)),
315 )
316 .await
317 {
318 Either::Left((left_result, right_fut)) => match left_result {
319 None | Some(ConfirmationAction::None) => right_fut.await,
320 _ => left_result,
321 },
322 Either::Right((right_result, left_fut)) => match right_result {
323 None | Some(ConfirmationAction::None) => left_fut.await,
324 _ => right_result,
325 },
326 }
327 }
328}