From 2e4058cfca52828a0b18daa48f1b41dd37e968aa Mon Sep 17 00:00:00 2001 From: Carson McManus Date: Sun, 8 Aug 2021 15:25:27 -0400 Subject: [PATCH] add add_authenticator to steamapi module --- steamguard/src/accountlinker.rs | 11 ++--- steamguard/src/lib.rs | 2 + steamguard/src/steamapi.rs | 82 +++++++++++++++++++++++++++++++++ 3 files changed, 88 insertions(+), 7 deletions(-) diff --git a/steamguard/src/accountlinker.rs b/steamguard/src/accountlinker.rs index e9da246..6359d13 100644 --- a/steamguard/src/accountlinker.rs +++ b/steamguard/src/accountlinker.rs @@ -1,5 +1,7 @@ -use crate::{steamapi::Session, SteamGuardAccount}; -use serde::Deserialize; +use crate::{ + steamapi::{AddAuthenticatorResponse, Session}, + SteamGuardAccount, +}; use std::collections::HashMap; use std::error::Error; use std::fmt::Display; @@ -55,11 +57,6 @@ fn generate_device_id() -> String { return format!("android:{}", uuid::Uuid::new_v4().to_string()); } -#[derive(Debug, Clone, Deserialize)] -pub struct AddAuthenticatorResponse { - pub response: SteamGuardAccount, -} - #[derive(Debug)] pub enum AccountLinkError { /// No phone number on the account diff --git a/steamguard/src/lib.rs b/steamguard/src/lib.rs index 8c91d3a..19b1a29 100644 --- a/steamguard/src/lib.rs +++ b/steamguard/src/lib.rs @@ -47,6 +47,7 @@ pub struct SteamGuardAccount { pub uri: String, pub fully_enrolled: bool, pub device_id: String, + pub secret_1: String, #[serde(rename = "Session")] pub session: Option, } @@ -84,6 +85,7 @@ impl SteamGuardAccount { uri: String::from(""), fully_enrolled: false, device_id: String::from(""), + secret_1: "".into(), session: Option::None, }; } diff --git a/steamguard/src/steamapi.rs b/steamguard/src/steamapi.rs index 0fda929..a429610 100644 --- a/steamguard/src/steamapi.rs +++ b/steamguard/src/steamapi.rs @@ -12,8 +12,11 @@ use std::iter::FromIterator; use std::str::FromStr; use std::time::{SystemTime, UNIX_EPOCH}; +use crate::SteamGuardAccount; + lazy_static! { static ref STEAM_COOKIE_URL: Url = "https://steamcommunity.com".parse::().unwrap(); + static ref STEAM_API_BASE: String = "https://api.steampowered.com".into(); } #[derive(Debug, Clone, Deserialize)] @@ -340,6 +343,37 @@ impl SteamApiClient { pub fn add_phone_number(&self, phone_number: String) -> anyhow::Result { return self.phoneajax("add_phone_number", phone_number.as_str()); } + + /// Starts the authenticator linking process. + /// This doesn't check any prereqisites to ensure the request will pass validation on Steam's side (eg. sms/email confirmations). + /// A valid `Session` is required for this request. Cookies are not needed for this request, but they are set anyway. + /// + /// Host: api.steampowered.com + /// Endpoint: POST /ITwoFactorService/AddAuthenticator/v0001 + pub fn add_authenticator(&self, device_id: String) -> anyhow::Result { + ensure!(matches!(self.session, Some(_))); + let params = hashmap! { + "access_token" => self.session.as_ref().unwrap().token.clone(), + "steamid" => self.session.as_ref().unwrap().steam_id.to_string(), + "authenticator_type" => "1".into(), + "device_identifier" => device_id, + "sms_phone_id" => "1".into(), + }; + + let text = self + .post(format!( + "{}/ITwoFactorService/AddAuthenticator/v0001", + STEAM_API_BASE.to_string() + )) + .form(¶ms) + .send()? + .text()?; + trace!("raw login response: {}", text); + + let resp: AddAuthenticatorResponse = serde_json::from_str(text.as_str())?; + + Ok(resp) + } } #[test] @@ -380,3 +414,51 @@ fn test_login_response_parse() { ); assert_eq!(oauth.webcookie, "6298070A226E5DAD49938D78BCF36F7A7118FDD5"); } + +#[derive(Debug, Clone, Deserialize)] +pub struct AddAuthenticatorResponse { + pub response: AddAuthenticatorResponseInner, +} + +#[derive(Debug, Clone, Deserialize)] +pub struct AddAuthenticatorResponseInner { + /// Shared secret between server and authenticator + pub shared_secret: String, + /// Authenticator serial number (unique per token) + pub serial_number: String, + /// code used to revoke authenticator + pub revocation_code: String, + /// URI for QR code generation + pub uri: String, + /// Current server time + pub server_time: u64, + /// Account name to display on token client + pub account_name: String, + /// Token GID assigned by server + pub token_gid: String, + /// Secret used for identity attestation (e.g., for eventing) + pub identity_secret: String, + /// Spare shared secret + pub secret_1: String, + /// Result code + pub status: String, +} + +impl AddAuthenticatorResponse { + pub fn to_steam_guard_account(&self) -> SteamGuardAccount { + SteamGuardAccount { + shared_secret: self.response.shared_secret, + serial_number: self.response.serial_number, + revocation_code: self.response.revocation_code, + uri: self.response.uri, + server_time: self.response.server_time, + account_name: self.response.account_name, + token_gid: self.response.token_gid, + identity_secret: self.response.identity_secret, + secret_1: self.response.secret_1, + fully_enrolled: false, + device_id: "".into(), + session: None, + } + } +}