Merge pull request #176 from dyc3/api-response-refactor
api response refactor
This commit is contained in:
commit
15bb6230f0
9 changed files with 313 additions and 283 deletions
|
@ -257,10 +257,12 @@ impl Manifest {
|
|||
}
|
||||
|
||||
/// Return all loaded accounts. Order is not guarenteed.
|
||||
#[allow(dead_code)]
|
||||
pub fn get_all_loaded(&self) -> Vec<Arc<Mutex<SteamGuardAccount>>> {
|
||||
return self.accounts.values().cloned().into_iter().collect();
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn get_entry(
|
||||
&self,
|
||||
account_name: &String,
|
||||
|
@ -271,6 +273,7 @@ impl Manifest {
|
|||
.ok_or(ManifestAccountLoadError::MissingManifestEntry)
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn get_entry_mut(
|
||||
&mut self,
|
||||
account_name: &String,
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
use crate::{
|
||||
steamapi::{
|
||||
AddAuthenticatorResponse, FinalizeAddAuthenticatorResponse, Session, SteamApiClient,
|
||||
},
|
||||
api_responses::{AddAuthenticatorResponse, FinalizeAddAuthenticatorResponse},
|
||||
steamapi::{Session, SteamApiClient},
|
||||
SteamGuardAccount,
|
||||
};
|
||||
use log::*;
|
||||
|
|
130
steamguard/src/api_responses/i_two_factor_service.rs
Normal file
130
steamguard/src/api_responses/i_two_factor_service.rs
Normal file
|
@ -0,0 +1,130 @@
|
|||
use crate::{token::TwoFactorSecret, SteamGuardAccount};
|
||||
|
||||
use super::parse_json_string_as_number;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
/// Represents the response from `/ITwoFactorService/QueryTime/v0001`
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct QueryTimeResponse {
|
||||
/// The time that the server will use to check your two factor code.
|
||||
#[serde(deserialize_with = "parse_json_string_as_number")]
|
||||
pub server_time: u64,
|
||||
#[serde(deserialize_with = "parse_json_string_as_number")]
|
||||
pub skew_tolerance_seconds: u64,
|
||||
#[serde(deserialize_with = "parse_json_string_as_number")]
|
||||
pub large_time_jink: u64,
|
||||
pub probe_frequency_seconds: u64,
|
||||
pub adjusted_time_probe_frequency_seconds: u64,
|
||||
pub hint_probe_frequency_seconds: u64,
|
||||
pub sync_timeout: u64,
|
||||
pub try_again_seconds: u64,
|
||||
pub max_attempts: u64,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize)]
|
||||
pub struct AddAuthenticatorResponse {
|
||||
/// Shared secret between server and authenticator
|
||||
#[serde(default)]
|
||||
pub shared_secret: String,
|
||||
/// Authenticator serial number (unique per token)
|
||||
#[serde(default)]
|
||||
pub serial_number: String,
|
||||
/// code used to revoke authenticator
|
||||
#[serde(default)]
|
||||
pub revocation_code: String,
|
||||
/// URI for QR code generation
|
||||
#[serde(default)]
|
||||
pub uri: String,
|
||||
/// Current server time
|
||||
#[serde(default, deserialize_with = "parse_json_string_as_number")]
|
||||
pub server_time: u64,
|
||||
/// Account name to display on token client
|
||||
#[serde(default)]
|
||||
pub account_name: String,
|
||||
/// Token GID assigned by server
|
||||
#[serde(default)]
|
||||
pub token_gid: String,
|
||||
/// Secret used for identity attestation (e.g., for eventing)
|
||||
#[serde(default)]
|
||||
pub identity_secret: String,
|
||||
/// Spare shared secret
|
||||
#[serde(default)]
|
||||
pub secret_1: String,
|
||||
/// Result code
|
||||
pub status: i32,
|
||||
#[serde(default)]
|
||||
pub phone_number_hint: Option<String>,
|
||||
}
|
||||
|
||||
impl AddAuthenticatorResponse {
|
||||
pub fn to_steam_guard_account(self) -> SteamGuardAccount {
|
||||
SteamGuardAccount {
|
||||
shared_secret: TwoFactorSecret::parse_shared_secret(self.shared_secret).unwrap(),
|
||||
serial_number: self.serial_number.clone(),
|
||||
revocation_code: self.revocation_code.into(),
|
||||
uri: self.uri.into(),
|
||||
server_time: self.server_time,
|
||||
account_name: self.account_name.clone(),
|
||||
token_gid: self.token_gid.clone(),
|
||||
identity_secret: self.identity_secret.into(),
|
||||
secret_1: self.secret_1.into(),
|
||||
fully_enrolled: false,
|
||||
device_id: "".into(),
|
||||
session: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize)]
|
||||
pub struct FinalizeAddAuthenticatorResponse {
|
||||
pub status: i32,
|
||||
#[serde(deserialize_with = "parse_json_string_as_number")]
|
||||
pub server_time: u64,
|
||||
pub want_more: bool,
|
||||
pub success: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize)]
|
||||
pub struct RemoveAuthenticatorResponse {
|
||||
pub success: bool,
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
use crate::api_responses::SteamApiResponse;
|
||||
|
||||
#[test]
|
||||
fn test_parse_add_auth_response() {
|
||||
let result = serde_json::from_str::<SteamApiResponse<AddAuthenticatorResponse>>(
|
||||
include_str!("../fixtures/api-responses/add-authenticator-1.json"),
|
||||
);
|
||||
|
||||
assert!(
|
||||
matches!(result, Ok(_)),
|
||||
"got error: {}",
|
||||
result.unwrap_err()
|
||||
);
|
||||
let resp = result.unwrap().response;
|
||||
|
||||
assert_eq!(resp.server_time, 1628559846);
|
||||
assert_eq!(resp.shared_secret, "wGwZx=sX5MmTxi6QgA3Gi");
|
||||
assert_eq!(resp.revocation_code, "R123456");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_add_auth_response2() {
|
||||
let result = serde_json::from_str::<SteamApiResponse<AddAuthenticatorResponse>>(
|
||||
include_str!("../fixtures/api-responses/add-authenticator-2.json"),
|
||||
);
|
||||
|
||||
assert!(
|
||||
matches!(result, Ok(_)),
|
||||
"got error: {}",
|
||||
result.unwrap_err()
|
||||
);
|
||||
let resp = result.unwrap().response;
|
||||
|
||||
assert_eq!(resp.status, 29);
|
||||
}
|
||||
}
|
137
steamguard/src/api_responses/login.rs
Normal file
137
steamguard/src/api_responses/login.rs
Normal file
|
@ -0,0 +1,137 @@
|
|||
use serde::{Deserialize, Deserializer, Serialize};
|
||||
|
||||
use super::parse_json_string_as_number;
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct LoginTransferParameters {
|
||||
pub steamid: String,
|
||||
pub token_secure: String,
|
||||
pub auth: String,
|
||||
pub remember_login: bool,
|
||||
pub webcookie: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize)]
|
||||
pub struct OAuthData {
|
||||
pub oauth_token: String,
|
||||
pub steamid: String,
|
||||
pub wgtoken: String,
|
||||
pub wgtoken_secure: String,
|
||||
#[serde(default)]
|
||||
pub webcookie: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize)]
|
||||
pub struct RsaResponse {
|
||||
pub success: bool,
|
||||
pub publickey_exp: String,
|
||||
pub publickey_mod: String,
|
||||
pub timestamp: String,
|
||||
pub token_gid: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize)]
|
||||
pub struct LoginResponse {
|
||||
pub success: bool,
|
||||
#[serde(default)]
|
||||
pub login_complete: bool,
|
||||
#[serde(default)]
|
||||
pub captcha_needed: bool,
|
||||
#[serde(default)]
|
||||
pub captcha_gid: String,
|
||||
#[serde(default, deserialize_with = "parse_json_string_as_number")]
|
||||
pub emailsteamid: u64,
|
||||
#[serde(default)]
|
||||
pub emailauth_needed: bool,
|
||||
#[serde(default)]
|
||||
pub requires_twofactor: bool,
|
||||
#[serde(default)]
|
||||
pub message: String,
|
||||
#[serde(default, deserialize_with = "oauth_data_from_string")]
|
||||
pub oauth: Option<OAuthData>,
|
||||
pub transfer_urls: Option<Vec<String>>,
|
||||
pub transfer_parameters: Option<LoginTransferParameters>,
|
||||
}
|
||||
|
||||
/// For some reason, the `oauth` field in the login response is a string of JSON, not a JSON object.
|
||||
/// Deserializes to `Option` because the `oauth` field is not always there.
|
||||
fn oauth_data_from_string<'de, D>(deserializer: D) -> Result<Option<OAuthData>, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
// for some reason, deserializing to &str doesn't work but this does.
|
||||
let s: String = Deserialize::deserialize(deserializer)?;
|
||||
let data: OAuthData = serde_json::from_str(s.as_str()).map_err(serde::de::Error::custom)?;
|
||||
Ok(Some(data))
|
||||
}
|
||||
|
||||
impl LoginResponse {
|
||||
pub fn needs_transfer_login(&self) -> bool {
|
||||
self.transfer_urls.is_some() || self.transfer_parameters.is_some()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_oauth_data_parse() {
|
||||
// This example is from a login response that did not contain any transfer URLs.
|
||||
let oauth: OAuthData = serde_json::from_str("{\"steamid\":\"78562647129469312\",\"account_name\":\"feuarus\",\"oauth_token\":\"fd2fdb3d0717bcd2220d98c7ec61c7bd\",\"wgtoken\":\"72E7013D598A4F68C7E268F6FA3767D89D763732\",\"wgtoken_secure\":\"21061EA13C36D7C29812CAED900A215171AD13A2\",\"webcookie\":\"6298070A226E5DAD49938D78BCF36F7A7118FDD5\"}").unwrap();
|
||||
|
||||
assert_eq!(oauth.steamid, "78562647129469312");
|
||||
assert_eq!(oauth.oauth_token, "fd2fdb3d0717bcd2220d98c7ec61c7bd");
|
||||
assert_eq!(oauth.wgtoken, "72E7013D598A4F68C7E268F6FA3767D89D763732");
|
||||
assert_eq!(
|
||||
oauth.wgtoken_secure,
|
||||
"21061EA13C36D7C29812CAED900A215171AD13A2"
|
||||
);
|
||||
assert_eq!(oauth.webcookie, "6298070A226E5DAD49938D78BCF36F7A7118FDD5");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_login_response_parse() {
|
||||
let result = serde_json::from_str::<LoginResponse>(include_str!(
|
||||
"../fixtures/api-responses/login-response1.json"
|
||||
));
|
||||
|
||||
assert!(
|
||||
matches!(result, Ok(_)),
|
||||
"got error: {}",
|
||||
result.unwrap_err()
|
||||
);
|
||||
let resp = result.unwrap();
|
||||
|
||||
let oauth = resp.oauth.unwrap();
|
||||
assert_eq!(oauth.steamid, "78562647129469312");
|
||||
assert_eq!(oauth.oauth_token, "fd2fdb3d0717bad2220d98c7ec61c7bd");
|
||||
assert_eq!(oauth.wgtoken, "72E7013D598A4F68C7E268F6FA3767D89D763732");
|
||||
assert_eq!(
|
||||
oauth.wgtoken_secure,
|
||||
"21061EA13C36D7C29812CAED900A215171AD13A2"
|
||||
);
|
||||
assert_eq!(oauth.webcookie, "6298070A226E5DAD49938D78BCF36F7A7118FDD5");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_login_response_parse_missing_webcookie() {
|
||||
let result = serde_json::from_str::<LoginResponse>(include_str!(
|
||||
"../fixtures/api-responses/login-response-missing-webcookie.json"
|
||||
));
|
||||
|
||||
assert!(
|
||||
matches!(result, Ok(_)),
|
||||
"got error: {}",
|
||||
result.unwrap_err()
|
||||
);
|
||||
let resp = result.unwrap();
|
||||
|
||||
let oauth = resp.oauth.unwrap();
|
||||
assert_eq!(oauth.steamid, "92591609556178617");
|
||||
assert_eq!(oauth.oauth_token, "1cc83205dab2979e558534dab29f6f3aa");
|
||||
assert_eq!(oauth.wgtoken, "3EDA9DEF07D7B39361D95203525D8AFE82A");
|
||||
assert_eq!(oauth.wgtoken_secure, "F31641B9AFC2F8B0EE7B6F44D7E73EA3FA48");
|
||||
assert_eq!(oauth.webcookie, "");
|
||||
}
|
||||
}
|
23
steamguard/src/api_responses/mod.rs
Normal file
23
steamguard/src/api_responses/mod.rs
Normal file
|
@ -0,0 +1,23 @@
|
|||
mod i_two_factor_service;
|
||||
mod login;
|
||||
mod phone_ajax;
|
||||
|
||||
pub use i_two_factor_service::*;
|
||||
pub use login::*;
|
||||
pub use phone_ajax::*;
|
||||
|
||||
use serde::{Deserialize, Deserializer};
|
||||
|
||||
pub(crate) fn parse_json_string_as_number<'de, D>(deserializer: D) -> Result<u64, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
// for some reason, deserializing to &str doesn't work but this does.
|
||||
let s: String = Deserialize::deserialize(deserializer)?;
|
||||
Ok(s.parse().unwrap())
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize)]
|
||||
pub struct SteamApiResponse<T> {
|
||||
pub response: T,
|
||||
}
|
11
steamguard/src/api_responses/phone_ajax.rs
Normal file
11
steamguard/src/api_responses/phone_ajax.rs
Normal file
|
@ -0,0 +1,11 @@
|
|||
use serde::Deserialize;
|
||||
|
||||
#[derive(Debug, Clone, Deserialize)]
|
||||
#[allow(dead_code)]
|
||||
pub struct PhoneValidateResponse {
|
||||
success: bool,
|
||||
number: String,
|
||||
is_valid: bool,
|
||||
is_voip: bool,
|
||||
is_fixed: bool,
|
||||
}
|
|
@ -4,7 +4,6 @@ use anyhow::Result;
|
|||
pub use confirmation::{Confirmation, ConfirmationType};
|
||||
use hmacsha1::hmac_sha1;
|
||||
use log::*;
|
||||
use regex::Regex;
|
||||
use reqwest::{
|
||||
cookie::CookieStore,
|
||||
header::{COOKIE, USER_AGENT},
|
||||
|
@ -24,6 +23,7 @@ extern crate anyhow;
|
|||
extern crate maplit;
|
||||
|
||||
mod accountlinker;
|
||||
mod api_responses;
|
||||
mod confirmation;
|
||||
mod secret_string;
|
||||
pub mod steamapi;
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
use crate::token::TwoFactorSecret;
|
||||
use crate::SteamGuardAccount;
|
||||
use crate::api_responses::*;
|
||||
use log::*;
|
||||
use reqwest::{
|
||||
blocking::RequestBuilder,
|
||||
|
@ -9,7 +8,7 @@ use reqwest::{
|
|||
Url,
|
||||
};
|
||||
use secrecy::{CloneableSecret, DebugSecret, ExposeSecret, SerializableSecret};
|
||||
use serde::{Deserialize, Deserializer, Serialize};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_json::Value;
|
||||
use std::iter::FromIterator;
|
||||
use std::str::FromStr;
|
||||
|
@ -21,77 +20,6 @@ lazy_static! {
|
|||
static ref STEAM_API_BASE: String = "https://api.steampowered.com".into();
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize)]
|
||||
pub struct LoginResponse {
|
||||
pub success: bool,
|
||||
#[serde(default)]
|
||||
pub login_complete: bool,
|
||||
#[serde(default)]
|
||||
pub captcha_needed: bool,
|
||||
#[serde(default)]
|
||||
pub captcha_gid: String,
|
||||
#[serde(default, deserialize_with = "parse_json_string_as_number")]
|
||||
pub emailsteamid: u64,
|
||||
#[serde(default)]
|
||||
pub emailauth_needed: bool,
|
||||
#[serde(default)]
|
||||
pub requires_twofactor: bool,
|
||||
#[serde(default)]
|
||||
pub message: String,
|
||||
// #[serde(rename = "oauth")]
|
||||
// oauth_raw: String,
|
||||
#[serde(default, deserialize_with = "oauth_data_from_string")]
|
||||
oauth: Option<OAuthData>,
|
||||
transfer_urls: Option<Vec<String>>,
|
||||
transfer_parameters: Option<LoginTransferParameters>,
|
||||
}
|
||||
|
||||
/// For some reason, the `oauth` field in the login response is a string of JSON, not a JSON object.
|
||||
/// Deserializes to `Option` because the `oauth` field is not always there.
|
||||
fn oauth_data_from_string<'de, D>(deserializer: D) -> Result<Option<OAuthData>, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
// for some reason, deserializing to &str doesn't work but this does.
|
||||
let s: String = Deserialize::deserialize(deserializer)?;
|
||||
let data: OAuthData = serde_json::from_str(s.as_str()).map_err(serde::de::Error::custom)?;
|
||||
Ok(Some(data))
|
||||
}
|
||||
|
||||
impl LoginResponse {
|
||||
pub fn needs_transfer_login(&self) -> bool {
|
||||
self.transfer_urls.is_some() || self.transfer_parameters.is_some()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
struct LoginTransferParameters {
|
||||
steamid: String,
|
||||
token_secure: String,
|
||||
auth: String,
|
||||
remember_login: bool,
|
||||
webcookie: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize)]
|
||||
pub struct RsaResponse {
|
||||
pub success: bool,
|
||||
pub publickey_exp: String,
|
||||
pub publickey_mod: String,
|
||||
pub timestamp: String,
|
||||
pub token_gid: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize)]
|
||||
pub struct OAuthData {
|
||||
oauth_token: String,
|
||||
steamid: String,
|
||||
wgtoken: String,
|
||||
wgtoken_secure: String,
|
||||
#[serde(default)]
|
||||
webcookie: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, Zeroize)]
|
||||
#[zeroize(drop)]
|
||||
pub struct Session {
|
||||
|
@ -113,24 +41,6 @@ impl SerializableSecret for Session {}
|
|||
impl CloneableSecret for Session {}
|
||||
impl DebugSecret for Session {}
|
||||
|
||||
/// Represents the response from `/ITwoFactorService/QueryTime/v0001`
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct QueryTimeResponse {
|
||||
/// The time that the server will use to check your two factor code.
|
||||
#[serde(deserialize_with = "parse_json_string_as_number")]
|
||||
pub server_time: u64,
|
||||
#[serde(deserialize_with = "parse_json_string_as_number")]
|
||||
pub skew_tolerance_seconds: u64,
|
||||
#[serde(deserialize_with = "parse_json_string_as_number")]
|
||||
pub large_time_jink: u64,
|
||||
pub probe_frequency_seconds: u64,
|
||||
pub adjusted_time_probe_frequency_seconds: u64,
|
||||
pub hint_probe_frequency_seconds: u64,
|
||||
pub sync_timeout: u64,
|
||||
pub try_again_seconds: u64,
|
||||
pub max_attempts: u64,
|
||||
}
|
||||
|
||||
/// Queries Steam for the current time.
|
||||
///
|
||||
/// Endpoint: `/ITwoFactorService/QueryTime/v0001`
|
||||
|
@ -565,189 +475,3 @@ impl SteamApiClient {
|
|||
return Ok(resp.response);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_oauth_data_parse() {
|
||||
// This example is from a login response that did not contain any transfer URLs.
|
||||
let oauth: OAuthData = serde_json::from_str("{\"steamid\":\"78562647129469312\",\"account_name\":\"feuarus\",\"oauth_token\":\"fd2fdb3d0717bcd2220d98c7ec61c7bd\",\"wgtoken\":\"72E7013D598A4F68C7E268F6FA3767D89D763732\",\"wgtoken_secure\":\"21061EA13C36D7C29812CAED900A215171AD13A2\",\"webcookie\":\"6298070A226E5DAD49938D78BCF36F7A7118FDD5\"}").unwrap();
|
||||
|
||||
assert_eq!(oauth.steamid, "78562647129469312");
|
||||
assert_eq!(oauth.oauth_token, "fd2fdb3d0717bcd2220d98c7ec61c7bd");
|
||||
assert_eq!(oauth.wgtoken, "72E7013D598A4F68C7E268F6FA3767D89D763732");
|
||||
assert_eq!(
|
||||
oauth.wgtoken_secure,
|
||||
"21061EA13C36D7C29812CAED900A215171AD13A2"
|
||||
);
|
||||
assert_eq!(oauth.webcookie, "6298070A226E5DAD49938D78BCF36F7A7118FDD5");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_login_response_parse() {
|
||||
let result = serde_json::from_str::<LoginResponse>(include_str!(
|
||||
"fixtures/api-responses/login-response1.json"
|
||||
));
|
||||
|
||||
assert!(
|
||||
matches!(result, Ok(_)),
|
||||
"got error: {}",
|
||||
result.unwrap_err()
|
||||
);
|
||||
let resp = result.unwrap();
|
||||
|
||||
let oauth = resp.oauth.unwrap();
|
||||
assert_eq!(oauth.steamid, "78562647129469312");
|
||||
assert_eq!(oauth.oauth_token, "fd2fdb3d0717bad2220d98c7ec61c7bd");
|
||||
assert_eq!(oauth.wgtoken, "72E7013D598A4F68C7E268F6FA3767D89D763732");
|
||||
assert_eq!(
|
||||
oauth.wgtoken_secure,
|
||||
"21061EA13C36D7C29812CAED900A215171AD13A2"
|
||||
);
|
||||
assert_eq!(oauth.webcookie, "6298070A226E5DAD49938D78BCF36F7A7118FDD5");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_login_response_parse_missing_webcookie() {
|
||||
let result = serde_json::from_str::<LoginResponse>(include_str!(
|
||||
"fixtures/api-responses/login-response-missing-webcookie.json"
|
||||
));
|
||||
|
||||
assert!(
|
||||
matches!(result, Ok(_)),
|
||||
"got error: {}",
|
||||
result.unwrap_err()
|
||||
);
|
||||
let resp = result.unwrap();
|
||||
|
||||
let oauth = resp.oauth.unwrap();
|
||||
assert_eq!(oauth.steamid, "92591609556178617");
|
||||
assert_eq!(oauth.oauth_token, "1cc83205dab2979e558534dab29f6f3aa");
|
||||
assert_eq!(oauth.wgtoken, "3EDA9DEF07D7B39361D95203525D8AFE82A");
|
||||
assert_eq!(oauth.wgtoken_secure, "F31641B9AFC2F8B0EE7B6F44D7E73EA3FA48");
|
||||
assert_eq!(oauth.webcookie, "");
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize)]
|
||||
pub struct SteamApiResponse<T> {
|
||||
pub response: T,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize)]
|
||||
pub struct AddAuthenticatorResponse {
|
||||
/// Shared secret between server and authenticator
|
||||
#[serde(default)]
|
||||
pub shared_secret: String,
|
||||
/// Authenticator serial number (unique per token)
|
||||
#[serde(default)]
|
||||
pub serial_number: String,
|
||||
/// code used to revoke authenticator
|
||||
#[serde(default)]
|
||||
pub revocation_code: String,
|
||||
/// URI for QR code generation
|
||||
#[serde(default)]
|
||||
pub uri: String,
|
||||
/// Current server time
|
||||
#[serde(default, deserialize_with = "parse_json_string_as_number")]
|
||||
pub server_time: u64,
|
||||
/// Account name to display on token client
|
||||
#[serde(default)]
|
||||
pub account_name: String,
|
||||
/// Token GID assigned by server
|
||||
#[serde(default)]
|
||||
pub token_gid: String,
|
||||
/// Secret used for identity attestation (e.g., for eventing)
|
||||
#[serde(default)]
|
||||
pub identity_secret: String,
|
||||
/// Spare shared secret
|
||||
#[serde(default)]
|
||||
pub secret_1: String,
|
||||
/// Result code
|
||||
pub status: i32,
|
||||
#[serde(default)]
|
||||
pub phone_number_hint: Option<String>,
|
||||
}
|
||||
|
||||
impl AddAuthenticatorResponse {
|
||||
pub fn to_steam_guard_account(self) -> SteamGuardAccount {
|
||||
SteamGuardAccount {
|
||||
shared_secret: TwoFactorSecret::parse_shared_secret(self.shared_secret).unwrap(),
|
||||
serial_number: self.serial_number.clone(),
|
||||
revocation_code: self.revocation_code.into(),
|
||||
uri: self.uri.into(),
|
||||
server_time: self.server_time,
|
||||
account_name: self.account_name.clone(),
|
||||
token_gid: self.token_gid.clone(),
|
||||
identity_secret: self.identity_secret.into(),
|
||||
secret_1: self.secret_1.into(),
|
||||
fully_enrolled: false,
|
||||
device_id: "".into(),
|
||||
session: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize)]
|
||||
pub struct FinalizeAddAuthenticatorResponse {
|
||||
pub status: i32,
|
||||
#[serde(deserialize_with = "parse_json_string_as_number")]
|
||||
pub server_time: u64,
|
||||
pub want_more: bool,
|
||||
pub success: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize)]
|
||||
#[allow(dead_code)]
|
||||
pub struct PhoneValidateResponse {
|
||||
success: bool,
|
||||
number: String,
|
||||
is_valid: bool,
|
||||
is_voip: bool,
|
||||
is_fixed: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize)]
|
||||
pub struct RemoveAuthenticatorResponse {
|
||||
pub success: bool,
|
||||
}
|
||||
|
||||
fn parse_json_string_as_number<'de, D>(deserializer: D) -> Result<u64, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
// for some reason, deserializing to &str doesn't work but this does.
|
||||
let s: String = Deserialize::deserialize(deserializer)?;
|
||||
Ok(s.parse().unwrap())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_add_auth_response() {
|
||||
let result = serde_json::from_str::<SteamApiResponse<AddAuthenticatorResponse>>(include_str!(
|
||||
"fixtures/api-responses/add-authenticator-1.json"
|
||||
));
|
||||
|
||||
assert!(
|
||||
matches!(result, Ok(_)),
|
||||
"got error: {}",
|
||||
result.unwrap_err()
|
||||
);
|
||||
let resp = result.unwrap().response;
|
||||
|
||||
assert_eq!(resp.server_time, 1628559846);
|
||||
assert_eq!(resp.shared_secret, "wGwZx=sX5MmTxi6QgA3Gi");
|
||||
assert_eq!(resp.revocation_code, "R123456");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_add_auth_response2() {
|
||||
let result = serde_json::from_str::<SteamApiResponse<AddAuthenticatorResponse>>(include_str!(
|
||||
"fixtures/api-responses/add-authenticator-2.json"
|
||||
));
|
||||
|
||||
assert!(
|
||||
matches!(result, Ok(_)),
|
||||
"got error: {}",
|
||||
result.unwrap_err()
|
||||
);
|
||||
let resp = result.unwrap().response;
|
||||
|
||||
assert_eq!(resp.status, 29);
|
||||
}
|
||||
|
|
|
@ -1,4 +1,7 @@
|
|||
use crate::steamapi::{LoginResponse, RsaResponse, Session, SteamApiClient};
|
||||
use crate::{
|
||||
api_responses::{LoginResponse, RsaResponse},
|
||||
steamapi::{Session, SteamApiClient},
|
||||
};
|
||||
use log::*;
|
||||
use rsa::{PublicKey, RsaPublicKey};
|
||||
use secrecy::ExposeSecret;
|
||||
|
|
Loading…
Reference in a new issue