steam_auth.rs 6.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198
  1. use crate::api_client::get_http_client;
  2. use serde::{Deserialize, Serialize};
  3. use std::collections::HashMap;
  4. use reqwest::Client;
  5. use base64::{Engine as _, engine::general_purpose};
  6. use hmac::{Hmac, Mac};
  7. use sha1::Sha1;
  8. use std::time::{SystemTime, UNIX_EPOCH};
  9. #[derive(Debug, Serialize, Deserialize)]
  10. pub struct SteamGuardAccount {
  11. pub shared_secret: String,
  12. pub identity_secret: String,
  13. pub account_name: String,
  14. pub device_id: String,
  15. pub session: SessionData,
  16. }
  17. #[derive(Debug, Serialize, Deserialize)]
  18. pub struct SessionData {
  19. pub steam_id: u64,
  20. pub access_token: String,
  21. pub refresh_token: String,
  22. }
  23. #[derive(Debug, Serialize, Deserialize)]
  24. pub struct Confirmation {
  25. pub id: String,
  26. pub key: String,
  27. pub conf_type: i32,
  28. pub creator: u64,
  29. pub headline: String,
  30. pub summary: Vec<String>,
  31. pub accept: String,
  32. pub cancel: String,
  33. pub icon: Option<String>,
  34. }
  35. #[derive(Debug, Serialize, Deserialize)]
  36. struct ConfirmationsResponse {
  37. success: bool,
  38. message: Option<String>,
  39. need_authentication: bool,
  40. confirmations: Vec<Confirmation>,
  41. }
  42. #[derive(Debug, Serialize, Deserialize)]
  43. struct SendConfirmationResponse {
  44. success: bool,
  45. }
  46. impl SteamGuardAccount {
  47. /// 生成Steam验证码
  48. pub fn generate_code(&self) -> Result<String, Box<dyn std::error::Error>> {
  49. let time = Self::get_steam_time();
  50. self.generate_code_for_time(time)
  51. }
  52. /// 为指定时间生成Steam验证码
  53. pub fn generate_code_for_time(&self, time: u64) -> Result<String, Box<dyn std::error::Error>> {
  54. let shared_secret = general_purpose::STANDARD.decode(&self.shared_secret)?;
  55. // 计算时间窗口
  56. let time_bytes = (time / 30).to_be_bytes();
  57. let mut time_buffer = [0u8; 8];
  58. time_buffer.copy_from_slice(&time_bytes[4..8]); // 只取后4字节
  59. // HMAC-SHA1
  60. let mut mac = Hmac::<Sha1>::new_from_slice(&shared_secret)?;
  61. mac.update(&time_buffer);
  62. let result = mac.finalize().into_bytes();
  63. // 动态截断
  64. let offset = (result[19] & 0x0F) as usize;
  65. let mut code_bytes = [0u8; 4];
  66. code_bytes.copy_from_slice(&result[offset..offset + 4]);
  67. let code = u32::from_be_bytes(code_bytes);
  68. // 生成5位验证码
  69. const STEAM_CHARS: &[u8] = b"23456789BCDFGHJKMNPQRTVWXY";
  70. let mut code_str = String::with_capacity(5);
  71. for i in 0..5 {
  72. let index = ((code >> (i * 5)) & 0x1F) as usize;
  73. code_str.push(STEAM_CHARS[index] as char);
  74. }
  75. Ok(code_str.chars().rev().collect())
  76. }
  77. /// 获取Steam服务器时间
  78. pub fn get_steam_time() -> u64 {
  79. let now = SystemTime::now()
  80. .duration_since(UNIX_EPOCH)
  81. .unwrap()
  82. .as_secs();
  83. // Steam时间偏移(通常需要从API获取,这里简化处理)
  84. now + 2 // 简单的2秒偏移
  85. }
  86. /// 生成确认查询参数
  87. fn generate_confirmation_query_params(&self, tag: &str, time: u64) -> HashMap<String, String> {
  88. let identity_secret = general_purpose::STANDARD.decode(&self.identity_secret).unwrap();
  89. // 生成确认哈希
  90. let time_bytes = time.to_be_bytes();
  91. let tag_bytes = tag.as_bytes();
  92. let mut data = Vec::with_capacity(8 + tag_bytes.len());
  93. data.extend_from_slice(&time_bytes);
  94. data.extend_from_slice(tag_bytes);
  95. let mut mac = Hmac::<Sha1>::new_from_slice(&identity_secret).unwrap();
  96. mac.update(&data);
  97. let hash = mac.finalize().into_bytes();
  98. let mut params = HashMap::new();
  99. params.insert("p".to_string(), self.device_id.clone());
  100. params.insert("a".to_string(), self.session.steam_id.to_string());
  101. params.insert("k".to_string(), general_purpose::STANDARD.encode(&hash));
  102. params.insert("t".to_string(), time.to_string());
  103. params.insert("m".to_string(), "react".to_string());
  104. params.insert("tag".to_string(), tag.to_string());
  105. params
  106. }
  107. /// 获取确认列表
  108. pub async fn fetch_confirmations(&self) -> Result<Vec<Confirmation>, Box<dyn std::error::Error>> {
  109. let client = get_http_client();
  110. let time = Self::get_steam_time();
  111. let params = self.generate_confirmation_query_params("conf", time);
  112. let query_string = params.iter()
  113. .map(|(k, v)| format!("{}={}", k, v))
  114. .collect::<Vec<_>>()
  115. .join("&");
  116. let url = format!("https://steamcommunity.com/mobileconf/getlist?{}", query_string);
  117. let response = client
  118. .get(&url)
  119. .header("User-Agent", "Mozilla/5.0 (Linux; Android 10; SM-G973F) AppleWebKit/537.36")
  120. .header("Cookie", format!("steamLoginSecure={}", self.session.access_token))
  121. .send()
  122. .await?;
  123. let response_text = response.text().await?;
  124. let confirmations_response: ConfirmationsResponse = serde_json::from_str(&response_text)?;
  125. if !confirmations_response.success {
  126. return Err(format!("Failed to fetch confirmations: {:?}", confirmations_response.message).into());
  127. }
  128. Ok(confirmations_response.confirmations)
  129. }
  130. /// 接受确认
  131. pub async fn accept_confirmation(&self, conf: &Confirmation) -> Result<bool, Box<dyn std::error::Error>> {
  132. self.send_confirmation_ajax(conf, "allow").await
  133. }
  134. /// 拒绝确认
  135. pub async fn deny_confirmation(&self, conf: &Confirmation) -> Result<bool, Box<dyn std::error::Error>> {
  136. self.send_confirmation_ajax(conf, "cancel").await
  137. }
  138. /// 发送确认操作
  139. async fn send_confirmation_ajax(&self, conf: &Confirmation, op: &str) -> Result<bool, Box<dyn std::error::Error>> {
  140. let client = get_http_client();
  141. let time = Self::get_steam_time();
  142. let tag = if op == "allow" { "accept" } else { "reject" };
  143. let mut params = self.generate_confirmation_query_params(tag, time);
  144. params.insert("op".to_string(), op.to_string());
  145. params.insert("cid".to_string(), conf.id.clone());
  146. params.insert("ck".to_string(), conf.key.clone());
  147. let query_string = params.iter()
  148. .map(|(k, v)| format!("{}={}", k, v))
  149. .collect::<Vec<_>>()
  150. .join("&");
  151. let url = format!("https://steamcommunity.com/mobileconf/ajaxop?{}", query_string);
  152. let response = client
  153. .get(&url)
  154. .header("User-Agent", "Mozilla/5.0 (Linux; Android 10; SM-G973F) AppleWebKit/537.36")
  155. .header("Cookie", format!("steamLoginSecure={}", self.session.access_token))
  156. .send()
  157. .await?;
  158. let response_text = response.text().await?;
  159. let conf_response: SendConfirmationResponse = serde_json::from_str(&response_text)?;
  160. Ok(conf_response.success)
  161. }
  162. }