add add_authenticator to steamapi module

This commit is contained in:
Carson McManus 2021-08-08 15:25:27 -04:00
parent e4d7ea4475
commit 2e4058cfca
3 changed files with 88 additions and 7 deletions

View file

@ -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

View file

@ -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<steamapi::Session>,
}
@ -84,6 +85,7 @@ impl SteamGuardAccount {
uri: String::from(""),
fully_enrolled: false,
device_id: String::from(""),
secret_1: "".into(),
session: Option::None,
};
}

View file

@ -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::<Url>().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<bool> {
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<AddAuthenticatorResponse> {
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(&params)
.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,
}
}
}