init_data_rust/
validate.rsuse std::{
collections::HashMap,
time::{Duration, SystemTime, UNIX_EPOCH},
};
use url::Url;
use crate::{errors::ValidationError, sign};
pub fn validate(
init_data: &str,
bot_token: &str,
exp_in: Duration,
) -> Result<bool, ValidationError> {
let url = Url::parse(&format!("http://dummy.com?{}", init_data))?;
let mut auth_date: Option<SystemTime> = None;
let mut hash: Option<String> = None;
let mut params: HashMap<String, String> = HashMap::new();
for (key, value) in url.query_pairs() {
let key_str = key.into_owned();
let value_str = value.into_owned();
if key_str == "hash" {
hash = Some(value_str.clone());
continue;
}
if key_str == "auth_date" {
if let Ok(timestamp) = value_str.parse::<u64>() {
auth_date = Some(UNIX_EPOCH + Duration::from_secs(timestamp));
}
}
params.insert(key_str, value_str);
}
let hash = hash.ok_or(ValidationError::SignMissing)?;
if exp_in != Duration::from_secs(0) {
let auth_date = auth_date.ok_or(ValidationError::AuthDateMissing)?;
if auth_date + exp_in < SystemTime::now() {
return Err(ValidationError::Expired);
}
}
let calculated_hash = sign(¶ms, bot_token, auth_date.unwrap_or(UNIX_EPOCH))
.map_err(|_| ValidationError::UnexpectedFormat)?;
if calculated_hash != hash {
return Err(ValidationError::SignInvalid);
}
Ok(true)
}
#[cfg(test)]
mod tests {
use super::*;
use std::time::Duration;
#[test]
fn test_validate_success() {
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";
let bot_token = "5768337691:AAH5YkoiEuPk8-FZa32hStHTqXiLPtAEhx8";
let exp_in = Duration::from_secs(1662771648);
assert!(validate(init_data, bot_token, exp_in).is_ok());
}
#[test]
fn test_validate_expired() {
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";
let bot_token = "your_bot_token";
let exp_in = Duration::from_secs(1);
assert!(matches!(
validate(init_data, bot_token, exp_in),
Err(ValidationError::Expired)
));
}
#[test]
fn test_validate_missing_hash() {
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";
let bot_token = "your_bot_token";
let exp_in = Duration::from_secs(0);
assert!(matches!(
validate(init_data, bot_token, exp_in),
Err(ValidationError::SignMissing)
));
}
#[test]
fn test_validate_invalid_signature() {
let init_data = "query_id=invalid&user=%7B%22id%22%3A12345%2C%22first_name%22%3A%22Test%22%7D&auth_date=1662771648&hash=invalidhash";
let bot_token = "5768337691:AAH5YkoiEuPk8-FZa32hStHTqXiLPtAEhx8";
let exp_in = Duration::from_secs(0);
assert!(matches!(
validate(init_data, bot_token, exp_in),
Err(ValidationError::SignInvalid)
));
}
}