init_data_rust/
validate.rs1use std::{
2 collections::HashMap,
3 time::{Duration, SystemTime, UNIX_EPOCH},
4};
5
6use url::Url;
7
8use crate::{errors::ValidationError, sign};
9
10pub fn validate(
12 init_data: &str,
13 bot_token: &str,
14 exp_in: Duration,
15) -> Result<bool, ValidationError> {
16 let url = Url::parse(&format!("http://dummy.com?{}", init_data))?;
18
19 let mut auth_date: Option<SystemTime> = None;
20 let mut hash: Option<String> = None;
21 let mut params: HashMap<String, String> = HashMap::new();
22
23 for (key, value) in url.query_pairs() {
25 let key_str = key.into_owned();
26 let value_str = value.into_owned();
27
28 if key_str == "hash" {
30 hash = Some(value_str.clone());
31 continue;
32 }
33 if key_str == "auth_date" {
34 if let Ok(timestamp) = value_str.parse::<u64>() {
35 auth_date = Some(UNIX_EPOCH + Duration::from_secs(timestamp));
36 }
37 }
38 params.insert(key_str, value_str);
40 }
41
42 let hash = hash.ok_or(ValidationError::SignMissing)?;
44
45 if exp_in != Duration::from_secs(0) {
47 let auth_date = auth_date.ok_or(ValidationError::AuthDateMissing)?;
49
50 if auth_date + exp_in < SystemTime::now() {
52 return Err(ValidationError::Expired);
53 }
54 }
55
56 let calculated_hash = sign(¶ms, bot_token, auth_date.unwrap_or(UNIX_EPOCH))
58 .map_err(|_| ValidationError::UnexpectedFormat)?;
59
60 if calculated_hash != hash {
62 return Err(ValidationError::SignInvalid);
63 }
64
65 Ok(true)
66}
67
68#[cfg(test)]
69mod tests {
70 use super::*;
71 use std::time::Duration;
72
73 #[test]
74 fn test_validate_success() {
75 let init_data = "query_id=AAHdF6IQAAAAAN0XohDhrOrc&user=%7B%22id%22%3A279058397%2C%22first_name%22%3A%22Vladislav%22%2C%22last_name%22%3A%22Kibenko%22%2C%22username%22%3A%22vdkfrost%22%2C%22language_code%22%3A%22ru%22%2C%22is_premium%22%3Atrue%7D&auth_date=1662771648&hash=c501b71e775f74ce10e377dea85a7ea24ecd640b223ea86dfe453e0eaed2e2b2";
76 let bot_token = "5768337691:AAH5YkoiEuPk8-FZa32hStHTqXiLPtAEhx8";
77 let exp_in = Duration::from_secs(1662771648);
78
79 assert!(validate(init_data, bot_token, exp_in).is_ok());
80 }
81
82 #[test]
83 fn test_validate_expired() {
84 let init_data = "query_id=AAHdF6IQAAAAAN0XohDhrOrc&user=%7B%22id%22%3A279058397%2C%22first_name%22%3A%22Vladislav%22%2C%22last_name%22%3A%22Kibenko%22%2C%22username%22%3A%22vdkfrost%22%2C%22language_code%22%3A%22ru%22%2C%22is_premium%22%3Atrue%7D&auth_date=1662771648&hash=c501b71e775f74ce10e377dea85a7ea24ecd640b223ea86dfe453e0eaed2e2b2";
85 let bot_token = "your_bot_token";
86 let exp_in = Duration::from_secs(1);
87
88 assert!(matches!(
89 validate(init_data, bot_token, exp_in),
90 Err(ValidationError::Expired)
91 ));
92 }
93
94 #[test]
95 fn test_validate_missing_hash() {
96 let init_data = "query_id=AAHdF6IQAAAAAN0XohDhrOrc&user=%7B%22id%22%3A279058397%2C%22first_name%22%3A%22Vladislav%22%2C%22last_name%22%3A%22Kibenko%22%2C%22username%22%3A%22vdkfrost%22%2C%22language_code%22%3A%22ru%22%2C%22is_premium%22%3Atrue%7D&auth_date=1662771648";
97 let bot_token = "your_bot_token";
98 let exp_in = Duration::from_secs(0);
99
100 assert!(matches!(
101 validate(init_data, bot_token, exp_in),
102 Err(ValidationError::SignMissing)
103 ));
104 }
105
106 #[test]
107 fn test_validate_invalid_signature() {
108 let init_data = "query_id=invalid&user=%7B%22id%22%3A12345%2C%22first_name%22%3A%22Test%22%7D&auth_date=1662771648&hash=invalidhash";
109 let bot_token = "5768337691:AAH5YkoiEuPk8-FZa32hStHTqXiLPtAEhx8";
110 let exp_in = Duration::from_secs(0);
111
112 assert!(matches!(
113 validate(init_data, bot_token, exp_in),
114 Err(ValidationError::SignInvalid)
115 ));
116 }
117}