tg_webapp_init_data/
lib.rs1use std::collections::BTreeMap;
4use std::time::{Duration, SystemTime};
5
6use serde::Deserialize;
7
8pub enum Error {
9 InvalidHash,
10 MissingField(&'static str),
11 InvalidJson(&'static str, serde_json::Error),
12 InvalidNumericField(&'static str),
13}
14
15#[derive(Debug)]
16pub struct WebAppInitData {
17 user: Option<WebAppUser>,
19 receiver: Option<WebAppUser>,
20 auth_date: u64,
26}
27
28#[derive(Debug, Deserialize)]
29pub struct WebAppUser {
30 id: i64,
31 is_bot: Option<bool>,
32 first_name: String,
33 last_name: Option<String>,
34 username: Option<String>,
35 language_code: Option<String>,
36 #[serde(default)]
37 is_premium: bool,
38 #[serde(default)]
39 added_to_attachment_menu: bool,
40 #[serde(default)]
41 allows_write_to_pm: bool,
42 photo_url: Option<String>,
43}
44
45impl WebAppInitData {
46 pub fn new(token: &str, raw: &[u8]) -> Result<Self, Error> {
47 let mut decoded: BTreeMap<_, _> = form_urlencoded::parse(raw).collect();
48 let hash = decoded.remove("hash").ok_or(Error::MissingField("hash"))?;
49
50 let mut data_check_string = String::new();
51 for (k, v) in &decoded {
52 if !data_check_string.is_empty() {
53 data_check_string.push('\n');
54 }
55 data_check_string.push_str(k);
56 data_check_string.push('=');
57 data_check_string.push_str(v);
58 }
59
60 let secret_key = hmac_sha256::HMAC::mac(token, "WebAppData");
61 let actual_hash = hmac_sha256::HMAC::mac(&data_check_string, secret_key);
62 if hex(&actual_hash) != *hash {
63 return Err(Error::InvalidHash);
64 }
65
66 Ok(WebAppInitData {
67 user: decoded
68 .remove("user")
69 .map(|x| serde_json::from_str(&x))
70 .transpose()
71 .map_err(|e| Error::InvalidJson("user", e))?,
72 receiver: decoded
73 .remove("receiver")
74 .map(|x| serde_json::from_str(&x))
75 .transpose()
76 .map_err(|e| Error::InvalidJson("receiver", e))?,
77 auth_date: decoded
78 .remove("auth_date")
79 .ok_or(Error::MissingField("auth_date"))?
80 .parse()
81 .map_err(|_e| Error::InvalidNumericField("auth_date"))?,
82 })
83 }
84
85 pub fn user(&self) -> Option<&WebAppUser> {
86 self.user.as_ref()
87 }
88
89 pub fn receiver(&self) -> Option<&WebAppUser> {
90 self.receiver.as_ref()
91 }
92
93 pub fn elapsed_since_auth(&self) -> Option<Duration> {
94 let now = SystemTime::now()
95 .duration_since(SystemTime::UNIX_EPOCH)
96 .ok()?
97 .as_secs();
98 let secs = now.checked_sub(self.auth_date)?;
99 Some(Duration::from_secs(secs))
100 }
101}
102
103impl WebAppUser {
104 pub fn id(&self) -> i64 {
105 self.id
106 }
107
108 pub fn is_bot(&self) -> Option<bool> {
109 self.is_bot
110 }
111
112 pub fn first_name(&self) -> &str {
113 &self.first_name
114 }
115
116 pub fn last_name(&self) -> Option<&str> {
117 self.last_name.as_deref()
118 }
119
120 pub fn username(&self) -> Option<&str> {
121 self.username.as_deref()
122 }
123
124 pub fn language_code(&self) -> Option<&str> {
125 self.language_code.as_deref()
126 }
127
128 pub fn is_premium(&self) -> bool {
129 self.is_premium
130 }
131
132 pub fn added_to_attachment_menu(&self) -> bool {
133 self.added_to_attachment_menu
134 }
135
136 pub fn allows_write_to_pm(&self) -> bool {
137 self.allows_write_to_pm
138 }
139
140 pub fn photo_url(&self) -> Option<&str> {
141 self.photo_url.as_deref()
142 }
143}
144
145fn hex(bytes: &[u8]) -> String {
146 let mut result = String::with_capacity(bytes.len() * 2);
147 for byte in bytes {
148 use std::fmt::Write;
149 let _ = write!(result, "{byte:02x}");
150 }
151 result
152}