use std::error::Error; use base64::{ engine::{self, general_purpose}, Engine, }; use chrono::{DateTime, Utc}; use hyper::http::HeaderValue; use serde::{Deserialize, Serialize}; #[derive(Deserialize, Serialize, Debug, Clone)] #[allow(dead_code)] struct JwtHeader { alg: String, typ: String, } #[derive(Deserialize, Serialize, Debug, Clone)] #[allow(dead_code)] enum ExpValue { Integer(i64), String(String), } #[derive(Deserialize, Serialize, Debug, Clone)] #[allow(dead_code)] struct JwtPayload { #[serde(deserialize_with = "deserialize_exp")] exp: i64, iss: Option, user: Option, } fn deserialize_exp<'de, D>(deserializer: D) -> Result where D: serde::Deserializer<'de>, { use serde::de::Error; #[derive(Deserialize)] #[serde(untagged)] enum ExpValue { String(String), Number(i64), } match ExpValue::deserialize(deserializer)? { ExpValue::String(s) => s.parse().map_err(D::Error::custom), ExpValue::Number(n) => Ok(n), } } #[derive(Deserialize, Serialize, Debug, Clone)] #[allow(dead_code)] pub struct JwtToken { header: JwtHeader, payload: JwtPayload, signature: Vec, expiration_date: DateTime, token: String, } impl JwtToken { fn decode_token_using_engine( token: &str, engine: engine::GeneralPurpose, ) -> Result> { let mut parts = token.split('.'); let header = parts.next().unwrap(); let payload = parts.next().unwrap(); let signature = parts.next().unwrap(); let header = engine.decode(header)?; let payload = engine.decode(payload)?; let signature = engine.decode(signature)?; // Parse jwt header let header: JwtHeader = serde_json::from_slice(&header)?; // Parse jwt payload let payload: JwtPayload = serde_json::from_slice(&payload)?; // Parse jwt expiration date let timestamp = DateTime::from_timestamp(payload.exp, 0).unwrap().naive_utc(); let expiration_date = DateTime::from_naive_utc_and_offset(timestamp, Utc); Ok(JwtToken { header, payload, signature, expiration_date, token: token.to_string(), }) } pub fn new(token: &str) -> Result> { // STUPID: My mock server uses a different encoding than the real server, so we have to // try both encodings here. log::debug!("Attempting to decode JWT token: {}", token); let result = Self::decode_token_using_engine(token, general_purpose::STANDARD).or( Self::decode_token_using_engine(token, general_purpose::URL_SAFE_NO_PAD), ); if let Err(ref e) = result { log::error!("Failed to decode JWT token: {}", e); log::error!("Token length: {}", token.len()); log::error!("Token parts: {:?}", token.split('.').collect::>()); } result } pub fn dummy() -> Self { JwtToken { header: JwtHeader { alg: "none".to_string(), typ: "JWT".to_string(), }, payload: JwtPayload { exp: 0, iss: None, user: None, }, signature: vec![], expiration_date: Utc::now(), token: "".to_string(), } } pub fn is_valid(&self) -> bool { self.expiration_date > Utc::now() } pub fn to_header_value(&self) -> HeaderValue { format!("Bearer {}", self.token).parse().unwrap() } pub fn to_string(&self) -> String { self.token.clone() } }