extern crate chrono;
extern crate hex;
extern crate ring;
extern crate serde;
#[macro_use]
extern crate serde_derive;
use ring::{digest, hmac};
#[derive(Serialize, Deserialize, Clone, Debug)]
pub struct TelegramLogin {
pub id: i32,
pub first_name: Option<String>,
pub last_name: Option<String>,
pub username: Option<String>,
pub photo_url: Option<String>,
pub hash: String,
#[serde(with = "unix_epoch")]
pub auth_date: chrono::NaiveDateTime,
}
mod unix_epoch {
use serde::{self, Deserialize, Deserializer, Serializer};
pub fn serialize<S>(date: &chrono::NaiveDateTime, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
let s = format!("{}", date.timestamp());
serializer.serialize_str(&s)
}
pub fn deserialize<'de, D>(deserializer: D) -> Result<chrono::NaiveDateTime, D::Error>
where
D: Deserializer<'de>,
{
match String::deserialize(deserializer) {
Ok(s) => match s.parse::<i64>() {
Ok(num_secs) => Ok(chrono::NaiveDateTime::from_timestamp(num_secs, 0)),
Err(_e) => Err(serde::de::Error::custom(
"auth-date param must be a unix epoche (int).",
)),
},
Err(_e) => Err(serde::de::Error::custom("auth-date param malformed.")),
}
}
}
#[derive(Clone, Debug, PartialEq)]
pub enum TelegramLoginError {
InvalidHash,
VerificationFailed,
}
pub fn check_signature(bot_token: String, user: TelegramLogin) -> Result<(), TelegramLoginError> {
match hex::decode(&user.hash) {
Ok(hash) => {
let data_check_string = gen_check_string(user);
let secret_key = digest::digest(&digest::SHA256, &bot_token.as_bytes());
let v_key = hmac::VerificationKey::new(&digest::SHA256, secret_key.as_ref());
hmac::verify(&v_key, data_check_string.as_bytes(), &hash)
.map_err(|_e| TelegramLoginError::VerificationFailed)
}
Err(_e) => Err(TelegramLoginError::InvalidHash),
}
}
fn gen_check_string(data: TelegramLogin) -> String {
struct Field<A> {
field: String,
value: Option<A>,
}
let fields = vec![
Field {
field: "auth_date".to_string(),
value: Some(data.auth_date.timestamp().to_string()),
},
Field {
field: "first_name".to_string(),
value: data.first_name,
},
Field {
field: "id".to_string(),
value: Some(data.id.to_string()),
},
Field {
field: "last_name".to_string(),
value: data.last_name,
},
Field {
field: "photo_url".to_string(),
value: data.photo_url,
},
Field {
field: "username".to_string(),
value: data.username,
},
];
let mut result = fields
.into_iter()
.fold("".to_string(), |acc, f| match f.value {
Some(val) => format!("{}{}={}\n", acc, f.field, val),
None => acc,
});
result.pop();
result
}
#[cfg(test)]
mod tests {
use super::*;
use chrono::NaiveDateTime;
#[test]
fn test_gen_check_string() {
let t_l = TelegramLogin {
id: 666666666,
username: Some("my_username".to_string()),
first_name: Some("Some".to_string()),
last_name: Some("Guy".to_string()),
photo_url: Some("https://t.me/i/userpic/320/my_username.jpg".to_string()),
auth_date: NaiveDateTime::from_timestamp(1543194375, 0),
hash: "aaaaaaaaaaaaaaaaaeeeeeeeeee4444444444444444444444444444444444444".to_string(),
};
assert_eq!(
gen_check_string(t_l),
"auth_date=1543194375\nfirst_name=Some\nid=666666666\nlast_name=Guy\nphoto_url=https://t.me/i/userpic/320/my_username.jpg\nusername=my_username"
);
}
#[test]
fn test_check_signature_success() {
let t_l = TelegramLogin {
id: 666666666,
username: Some("my_username".to_string()),
first_name: Some("Some".to_string()),
last_name: Some("Guy".to_string()),
photo_url: Some("https://t.me/i/userpic/320/my_username.jpg".to_string()),
auth_date: NaiveDateTime::from_timestamp(1543194375, 0),
hash: "a9cf12636fb07b54b4c95673d017a72364472c41a760b6850bcd5405da769f80".to_string(),
};
assert_eq!(
check_signature(
"777777777:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa".to_string(),
t_l
),
Ok(())
);
}
#[test]
fn test_check_signature_err_verification_failed() {
let t_l = TelegramLogin {
id: 666666666,
username: Some("my_username".to_string()),
first_name: Some("Some".to_string()),
last_name: Some("Guy".to_string()),
photo_url: Some("https://t.me/i/userpic/320/my_username.jpg".to_string()),
auth_date: NaiveDateTime::from_timestamp(1543194375, 0),
hash: "aaaaaaaaaaaaaaaaaeeeeeeeeee4444444444444444444444444444444444444".to_string(),
};
assert_eq!(
check_signature(
"777777777:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa".to_string(),
t_l
),
Err(TelegramLoginError::VerificationFailed)
);
}
#[test]
fn test_check_signature_err_invalid_hash() {
let t_l = TelegramLogin {
id: 666666666,
username: Some("my_username".to_string()),
first_name: Some("Some".to_string()),
last_name: Some("Guy".to_string()),
photo_url: Some("https://t.me/i/userpic/320/my_username.jpg".to_string()),
auth_date: NaiveDateTime::from_timestamp(1543194375, 0),
hash: "xxxxxxxaaaaaaaaaaeeeeeeeeee4444444444444444444444444444444444444".to_string(),
};
assert_eq!(
check_signature(
"777777777:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa".to_string(),
t_l
),
Err(TelegramLoginError::InvalidHash)
);
}
}