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 { exp: serde_json::Value, iss: Option, user: Option, } #[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 // Annoyingly, because of my own fault, this could be either an integer or string. let exp: i64 = payload.exp.as_i64().unwrap_or_else(|| { let exp: String = payload.exp.as_str().unwrap().to_string(); exp.parse().unwrap() }); let timestamp = DateTime::from_timestamp(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. Self::decode_token_using_engine(token, general_purpose::STANDARD).or( Self::decode_token_using_engine(token, general_purpose::URL_SAFE_NO_PAD), ) } pub fn dummy() -> Self { JwtToken { header: JwtHeader { alg: "none".to_string(), typ: "JWT".to_string(), }, payload: JwtPayload { exp: serde_json::Value::Null, 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() } }