diff --git a/Cargo.toml b/Cargo.toml index 996e357..5fed32a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -24,4 +24,6 @@ serde_json = "1.0" hyper = { version = "0.14", features = ["http1"] } hyper-tls = "0.5" tokio = { version = "1", features = ["rt-multi-thread","net","macros"] } -thiserror = "1.0" \ No newline at end of file +thiserror = "1.0" +lazy_static = "1.4.0" +async_once = "0.2.6" diff --git a/src/lib.rs b/src/lib.rs index 7411826..eaf1114 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -23,8 +23,23 @@ use jsonwebtoken::{ }; use serde::de::DeserializeOwned; use std::collections::HashMap; +use lazy_static::lazy_static; +use async_once::AsyncOnce; +use tokio::time::sleep; +use core::time::Duration; + +lazy_static! { + static ref APPLE_KEYS : AsyncOnce> = AsyncOnce::new(async { + loop { + if let Ok(keys) = fetch_apple_keys().await { + break keys; + } else { + sleep(Duration::from_secs(1)).await + } + } + }); +} -//TODO: put verification into a struct and only fetch apple keys once in the beginning async fn fetch_apple_keys() -> Result> { let https = HttpsConnector::new(); @@ -51,17 +66,17 @@ async fn fetch_apple_keys() -> Result> /// decoe token with optional expiry validation pub async fn decode_token( - token: String, + token: &str, ignore_expire: bool, ) -> Result> { - let header = decode_header(token.as_str())?; + let header = decode_header(token)?; let kid = match header.kid { Some(k) => k, None => return Err(Error::KidNotFound), }; - let pubkeys = fetch_apple_keys().await?; + let pubkeys = APPLE_KEYS.get().await; let pubkey = match pubkeys.get(&kid) { Some(key) => key, @@ -71,7 +86,7 @@ pub async fn decode_token( let mut val = Validation::new(header.alg); val.validate_exp = !ignore_expire; let token_data = decode::( - token.as_str(), + token, &DecodingKey::from_rsa_components(&pubkey.n, &pubkey.e) .unwrap(), &val, @@ -81,8 +96,8 @@ pub async fn decode_token( } pub async fn validate( - client_id: String, - token: String, + client_id: &str, + token: &str, ignore_expire: bool, ) -> Result> { let token_data = @@ -93,7 +108,7 @@ pub async fn validate( return Err(Error::IssClaimMismatch); } - if token_data.claims.sub != client_id { + if token_data.claims.aud != client_id { return Err(Error::ClientIdMismatch); } Ok(token_data) @@ -125,14 +140,15 @@ mod tests { async fn validate_test() -> std::result::Result<(), Error> { let user_token = "001026.16112b36378440d995af22b268f00984.1744"; + let client_id = "com.gameroasters.stack4"; let token = "eyJraWQiOiJZdXlYb1kiLCJhbGciOiJSUzI1NiJ9.eyJpc3MiOiJodHRwczovL2FwcGxlaWQuYXBwbGUuY29tIiwiYXVkIjoiY29tLmdhbWVyb2FzdGVycy5zdGFjazQiLCJleHAiOjE2MTQ1MTc1OTQsImlhdCI6MTYxNDQzMTE5NCwic3ViIjoiMDAxMDI2LjE2MTEyYjM2Mzc4NDQwZDk5NWFmMjJiMjY4ZjAwOTg0LjE3NDQiLCJjX2hhc2giOiJNNVVDdW5GdTFKNjdhdVE2LXEta093IiwiZW1haWwiOiJ6ZGZ1N2p0dXVzQHByaXZhdGVyZWxheS5hcHBsZWlkLmNvbSIsImVtYWlsX3ZlcmlmaWVkIjoidHJ1ZSIsImlzX3ByaXZhdGVfZW1haWwiOiJ0cnVlIiwiYXV0aF90aW1lIjoxNjE0NDMxMTk0LCJub25jZV9zdXBwb3J0ZWQiOnRydWV9.GuMJfVbnEvqppwwHFZjn3GDJtB4c4rl7C4PZzyDsdyiuXcFcXq52Ti0WSJBsqtfyT2dXvYxVxebHtONSQha_9DiM5qfYTZbpDDlIXrOMy1fkfStocold_wHWavofIpoJQVUMj45HLHtjixiNE903Pho6eY2UjEUjB3aFe8txuFIMv2JsaMCYzG4-e632FKBn63SroCkLc-8b4EVV4iYqnC5AfZArXhVjUevhhlaBH0E8Az2OGEe74U2WgBvMXEilmd62Ek-uInnrpJRgYQfYXvehQ1yT3aMiIgJICTQFMDdL1KAvs6mc081lNJLFYvViWlMH-Y7E5ajtUiMApiNYsg"; let result = - validate(user_token.to_string(), token.to_string(), true) + validate(client_id, token, true) .await?; assert_eq!(result.claims.sub, user_token); - assert_eq!(result.claims.aud, "com.gameroasters.stack4"); + assert_eq!(result.claims.aud, client_id); Ok(()) } @@ -142,12 +158,12 @@ mod tests { let token = "eyJraWQiOiJlWGF1bm1MIiwiYWxnIjoiUlMyNTYifQ.eyJpc3MiOiJodHRwczovL2FwcGxlaWQuYXBwbGUuY29tIiwiYXVkIjoiY29tLmdhbWVyb2FzdGVycy5zdGFjazQiLCJleHAiOjE2MzA4Mjc4MzAsImlhdCI6MTYzMDc0MTQzMCwic3ViIjoiMDAxMDI2LjE2MTEyYjM2Mzc4NDQwZDk5NWFmMjJiMjY4ZjAwOTg0LjE3NDQiLCJjX2hhc2giOiI0QjZKWTU4TmstVUJsY3dMa2VLc2lnIiwiYXV0aF90aW1lIjoxNjMwNzQxNDMwLCJub25jZV9zdXBwb3J0ZWQiOnRydWV9.iW0xk__fPD0mlh9UU-vh9VnR8yekWq64sl5re5d7UmDJxb1Fzk1Kca-hkA_Ka1LhSmKADdFW0DYEZhckqh49DgFtFdx6hM9t7guK3yrvBglhF5LAyb8NR028npxioLTTIgP_aR6Bpy5AyLQrU-yYEx2WTPYV5ln9n8vW154gZKRyl2KBlj9fS11BL_X1UFbFrL21GG_iPbB4qt5ywwTPoJ-diGN5JQzP5fk4yU4e4YmHhxJrT0NTTux2mB3lGJLa6YN-JYe_BuVV9J-sg_2r_ugTOUp3xQpfntu8xgQrY5W0oPxAPM4sibNLsye2kgPYYxfRYowc0JIjOcOd_JHDbQ"; validate( - "001026.16112b36378440d995af22b268f00984.1744".into(), - token.to_string(), + "com.gameroasters.stack4", + token, true, ) - .await - .unwrap(); + .await + .unwrap(); } #[tokio::test] @@ -155,11 +171,11 @@ mod tests { let token = "eyJraWQiOiJlWGF1bm1MIiwiYWxnIjoiUlMyNTYifQ.eyJpc3MiOiJodHRwczovL2FwcGxlaWQuYXBwbGUuY29tIiwiYXVkIjoiY29tLmdhbWVyb2FzdGVycy5zdGFjazQiLCJleHAiOjE2MzA4Mjc4MzAsImlhdCI6MTYzMDc0MTQzMCwic3ViIjoiMDAxMDI2LjE2MTEyYjM2Mzc4NDQwZDk5NWFmMjJiMjY4ZjAwOTg0LjE3NDQiLCJjX2hhc2giOiI0QjZKWTU4TmstVUJsY3dMa2VLc2lnIiwiYXV0aF90aW1lIjoxNjMwNzQxNDMwLCJub25jZV9zdXBwb3J0ZWQiOnRydWV9.iW0xk__fPD0mlh9UU-vh9VnR8yekWq64sl5re5d7UmDJxb1Fzk1Kca-hkA_Ka1LhSmKADdFW0DYEZhckqh49DgFtFdx6hM9t7guK3yrvBglhF5LAyb8NR028npxioLTTIgP_aR6Bpy5AyLQrU-yYEx2WTPYV5ln9n8vW154gZKRyl2KBlj9fS11BL_X1UFbFrL21GG_iPbB4qt5ywwTPoJ-diGN5JQzP5fk4yU4e4YmHhxJrT0NTTux2mB3lGJLa6YN-JYe_BuVV9J-sg_2r_ugTOUp3xQpfntu8xgQrY5W0oPxAPM4sibNLsye2kgPYYxfRYowc0JIjOcOd_JHDbQ"; let res = validate( - "001026.16112b36378440d995af22b268f00984.1744".into(), - token.to_string(), + "com.gameroasters.stack4", + token, false, ) - .await; + .await; assert!(is_expired(&res)); } @@ -169,11 +185,11 @@ mod tests { let token = "eyJraWQiOiJlWGF1bm1MIiwiYWxnIjoiUlMyNTYifQ.eyJpc3MiOiJodHRwczovL2FwcGxlaWQuYXBwbGUuY29tIiwiYXVkIjoiY29tLmdhbWVyb2FzdGVycy5zdGFjazQiLCJleHAiOjE2MzAxNzE4MTIsImlhdCI6MTYzMDA4NTQxMiwianRpIjoiQjk0T2REMDNwRnNhWWFOLUZ0djdtQSIsImV2ZW50cyI6IntcInR5cGVcIjpcImVtYWlsLWRpc2FibGVkXCIsXCJzdWJcIjpcIjAwMTAyNi4xNjExMmIzNjM3ODQ0MGQ5OTVhZjIyYjI2OGYwMDk4NC4xNzQ0XCIsXCJldmVudF90aW1lXCI6MTYzMDA4NTQwMzY0OCxcImVtYWlsXCI6XCJ6ZGZ1N2p0dXVzQHByaXZhdGVyZWxheS5hcHBsZWlkLmNvbVwiLFwiaXNfcHJpdmF0ZV9lbWFpbFwiOlwidHJ1ZVwifSJ9.SSdUM88GHqrS0QXHtaehbPxLQkAB3s1-qzcy3i2iRoSCzDhA1Q3o_FhiCbqOsbiPDOQ9aA1Z8-oAz1p3-TMfHy6QdIs1vLxBmNTe5IazNJw_7wwDZG2nr-bsKPUQldE--tK1EUFXQqQxQbfjJJE0JFEwPib2rmnb-t0mRopKMx2wg3CUlI64BHI2O8giGCbWB7UbJs2BpcUuapVShCIR7Eqxy0_ud81CUDjKzZK2CcmSRGDIk8g9pRqOHmPUFMOrDjj6_hUR9mf-xCrCedoC9f05z_yKD026A4gWGFn4pxTP8-uDTRPxcONax_vnQHBUDigYi8HXuzWorTx2ORPjaw"; let result = decode_token::( - token.to_string(), + token, true, ) - .await - .unwrap(); + .await + .unwrap(); assert_eq!(result.claims.aud, "com.gameroasters.stack4"); assert_eq!(